home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / DB / DataObject.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  141.5 KB  |  4,083 lines

  1. <?php
  2. /**
  3.  * Object Based Database Query Builder and data store
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   Database
  14.  * @package    DB_DataObject
  15.  * @author     Alan Knowles <alan@akbkhome.com>
  16.  * @copyright  1997-2006 The PHP Group
  17.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  18.  * @version    CVS: $Id: DataObject.php,v 1.423 2006/12/05 08:35:32 alan_k Exp $
  19.  * @link       http://pear.php.net/package/DB_DataObject
  20.  */
  21.   
  22.  
  23. /* =========================================================================== 
  24.  *
  25.  *    !!!!!!!!!!!!!               W A R N I N G                !!!!!!!!!!!
  26.  *
  27.  *  THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it, 
  28.  *  just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include 
  29.  *  this file. reducing the optimization level may also solve the segfault.
  30.  *  ===========================================================================
  31.  */
  32.  
  33. /**
  34.  * The main "DB_DataObject" class is really a base class for your own tables classes
  35.  *
  36.  * // Set up the class by creating an ini file (refer to the manual for more details
  37.  * [DB_DataObject]
  38.  * database         = mysql:/username:password@host/database
  39.  * schema_location = /home/myapplication/database
  40.  * class_location  = /home/myapplication/DBTables/
  41.  * clase_prefix    = DBTables_
  42.  *
  43.  *
  44.  * //Start and initialize...................... - dont forget the &
  45.  * $config = parse_ini_file('example.ini',true);
  46.  * $options = &PEAR::getStaticProperty('DB_DataObject','options');
  47.  * $options = $config['DB_DataObject'];
  48.  *
  49.  * // example of a class (that does not use the 'auto generated tables data')
  50.  * class mytable extends DB_DataObject {
  51.  *     // mandatory - set the table
  52.  *     var $_database_dsn = "mysql://username:password@localhost/database";
  53.  *     var $__table = "mytable";
  54.  *     function table() {
  55.  *         return array(
  56.  *             'id' => 1, // integer or number
  57.  *             'name' => 2, // string
  58.  *        );
  59.  *     }
  60.  *     function keys() {
  61.  *         return array('id');
  62.  *     }
  63.  * }
  64.  *
  65.  * // use in the application
  66.  *
  67.  *
  68.  * Simple get one row
  69.  *
  70.  * $instance = new mytable;
  71.  * $instance->get("id",12);
  72.  * echo $instance->somedata;
  73.  *
  74.  *
  75.  * Get multiple rows
  76.  *
  77.  * $instance = new mytable;
  78.  * $instance->whereAdd("ID > 12");
  79.  * $instance->whereAdd("ID < 14");
  80.  * $instance->find();
  81.  * while ($instance->fetch()) {
  82.  *     echo $instance->somedata;
  83.  * }
  84.  
  85.  
  86. /**
  87.  * Needed classes
  88.  * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM)
  89.  */
  90.  
  91. require_once 'PEAR.php';
  92.  
  93. /**
  94.  * We are duping fetchmode constants to be compatible with
  95.  * both DB and MDB2
  96.  */
  97. define('DB_DATAOBJECT_FETCHMODE_ORDERED',1); 
  98. define('DB_DATAOBJECT_FETCHMODE_ASSOC',2);
  99.  
  100.  
  101.  
  102.  
  103.  
  104. /**
  105.  * these are constants for the get_table array
  106.  * user to determine what type of escaping is required around the object vars.
  107.  */
  108. define('DB_DATAOBJECT_INT',  1);  // does not require ''
  109. define('DB_DATAOBJECT_STR',  2);  // requires ''
  110.  
  111. define('DB_DATAOBJECT_DATE', 4);  // is date #TODO
  112. define('DB_DATAOBJECT_TIME', 8);  // is time #TODO
  113. define('DB_DATAOBJECT_BOOL', 16); // is boolean #TODO
  114. define('DB_DATAOBJECT_TXT',  32); // is long text #TODO
  115. define('DB_DATAOBJECT_BLOB', 64); // is blob type
  116.  
  117.  
  118. define('DB_DATAOBJECT_NOTNULL', 128);           // not null col.
  119. define('DB_DATAOBJECT_MYSQLTIMESTAMP'   , 256);           // mysql timestamps (ignored by update/insert)
  120. /*
  121.  * Define this before you include DataObjects.php to  disable overload - if it segfaults due to Zend optimizer..
  122.  */
  123. //define('DB_DATAOBJECT_NO_OVERLOAD',true)  
  124.  
  125.  
  126. /**
  127.  * Theses are the standard error codes, most methods will fail silently - and return false
  128.  * to access the error message either use $table->_lastError
  129.  * or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
  130.  * the code is $last_error->code, and the message is $last_error->message (a standard PEAR error)
  131.  */
  132.  
  133. define('DB_DATAOBJECT_ERROR_INVALIDARGS',   -1);  // wrong args to function
  134. define('DB_DATAOBJECT_ERROR_NODATA',        -2);  // no data available
  135. define('DB_DATAOBJECT_ERROR_INVALIDCONFIG', -3);  // something wrong with the config
  136. define('DB_DATAOBJECT_ERROR_NOCLASS',       -4);  // no class exists
  137. define('DB_DATAOBJECT_ERROR_INVALID_CALL'  ,-7);  // overlad getter/setter failure
  138.  
  139. /**
  140.  * Used in methods like delete() and count() to specify that the method should
  141.  * build the condition only out of the whereAdd's and not the object parameters.
  142.  */
  143. define('DB_DATAOBJECT_WHEREADD_ONLY', true);
  144.  
  145. /**
  146.  *
  147.  * storage for connection and result objects,
  148.  * it is done this way so that print_r()'ing the is smaller, and
  149.  * it reduces the memory size of the object.
  150.  * -- future versions may use $this->_connection = & PEAR object..
  151.  *   although will need speed tests to see how this affects it.
  152.  * - includes sub arrays
  153.  *   - connections = md5 sum mapp to pear db object
  154.  *   - results     = [id] => map to pear db object
  155.  *   - resultseq   = sequence id for results & results field
  156.  *   - resultfields = [id] => list of fields return from query (for use with toArray())
  157.  *   - ini         = mapping of database to ini file results
  158.  *   - links       = mapping of database to links file
  159.  *   - lasterror   = pear error objects for last error event.
  160.  *   - config      = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance.
  161.  *   - array of loaded classes by autoload method - to stop it doing file access request over and over again!
  162.  */
  163. $GLOBALS['_DB_DATAOBJECT']['RESULTS']   = array();
  164. $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1;
  165. $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array();
  166. $GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array();
  167. $GLOBALS['_DB_DATAOBJECT']['INI'] = array();
  168. $GLOBALS['_DB_DATAOBJECT']['LINKS'] = array();
  169. $GLOBALS['_DB_DATAOBJECT']['SEQUENCE'] = array();
  170. $GLOBALS['_DB_DATAOBJECT']['LASTERROR'] = null;
  171. $GLOBALS['_DB_DATAOBJECT']['CONFIG'] = array();
  172. $GLOBALS['_DB_DATAOBJECT']['CACHE'] = array();
  173. $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false;
  174. $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0;
  175.  
  176.  
  177.  
  178. // this will be horrifically slow!!!!
  179. // NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..)
  180. // these two are BC/FC handlers for call in PHP4/5
  181.  
  182. if ( substr(phpversion(),0,1) == 5) {
  183.     class DB_DataObject_Overload 
  184.     {
  185.         function __call($method,$args) 
  186.         {
  187.             $return = null;
  188.             $this->_call($method,$args,$return);
  189.             return $return;
  190.         }
  191.         function __sleep() 
  192.         {
  193.             return array_keys(get_object_vars($this)) ; 
  194.         }
  195.     }
  196. } else {
  197.     if (version_compare(phpversion(),'4.3.10','eq') && !defined('DB_DATAOBJECT_NO_OVERLOAD')) {
  198.         trigger_error(
  199.             "overload does not work with PHP4.3.10, either upgrade 
  200.             (snaps.php.net) or more recent version 
  201.             or define DB_DATAOBJECT_NO_OVERLOAD as per the manual.
  202.             ",E_USER_ERROR);
  203.     }
  204.  
  205.     if (!function_exists('clone')) {
  206.         // emulate clone  - as per php_compact, slow but really the correct behaviour..
  207.         eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }');
  208.     }
  209.     eval('
  210.         class DB_DataObject_Overload {
  211.             function __call($method,$args,&$return) {
  212.                 return $this->_call($method,$args,$return); 
  213.             }
  214.         }
  215.     ');
  216. }
  217.  
  218.     
  219.  
  220.  
  221.  
  222.  
  223.  /*
  224.  *
  225.  * @package  DB_DataObject
  226.  * @author   Alan Knowles <alan@akbkhome.com>
  227.  * @since    PHP 4.0
  228.  */
  229.  
  230. class DB_DataObject extends DB_DataObject_Overload
  231. {
  232.    /**
  233.     * The Version - use this to check feature changes
  234.     *
  235.     * @access   private
  236.     * @var      string
  237.     */
  238.     var $_DB_DataObject_version = "1.8.5";
  239.  
  240.     /**
  241.      * The Database table (used by table extends)
  242.      *
  243.      * @access  private
  244.      * @var     string
  245.      */
  246.     var $__table = '';  // database table
  247.  
  248.     /**
  249.      * The Number of rows returned from a query
  250.      *
  251.      * @access  public
  252.      * @var     int
  253.      */
  254.     var $N = 0;  // Number of rows returned from a query
  255.  
  256.     /* ============================================================= */
  257.     /*                      Major Public Methods                     */
  258.     /* (designed to be optionally then called with parent::method()) */
  259.     /* ============================================================= */
  260.  
  261.  
  262.     /**
  263.      * Get a result using key, value.
  264.      *
  265.      * for example
  266.      * $object->get("ID",1234);
  267.      * Returns Number of rows located (usually 1) for success,
  268.      * and puts all the table columns into this classes variables
  269.      *
  270.      * see the fetch example on how to extend this.
  271.      *
  272.      * if no value is entered, it is assumed that $key is a value
  273.      * and get will then use the first key in keys()
  274.      * to obtain the key.
  275.      *
  276.      * @param   string  $k column
  277.      * @param   string  $v value
  278.      * @access  public
  279.      * @return  int     No. of rows
  280.      */
  281.     function get($k = null, $v = null)
  282.     {
  283.         global $_DB_DATAOBJECT;
  284.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  285.             DB_DataObject::_loadConfig();
  286.         }
  287.         $keys = array();
  288.         
  289.         if ($v === null) {
  290.             $v = $k;
  291.             $keys = $this->keys();
  292.             if (!$keys) {
  293.                 $this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  294.                 return false;
  295.             }
  296.             $k = $keys[0];
  297.         }
  298.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  299.             $this->debug("$k $v " .print_r($keys,true), "GET");
  300.         }
  301.         
  302.         if ($v === null) {
  303.             $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS);
  304.             return false;
  305.         }
  306.         $this->$k = $v;
  307.         return $this->find(1);
  308.     }
  309.  
  310.     /**
  311.      * An autoloading, caching static get method  using key, value (based on get)
  312.      *
  313.      * Usage:
  314.      * $object = DB_DataObject::staticGet("DbTable_mytable",12);
  315.      * or
  316.      * $object =  DB_DataObject::staticGet("DbTable_mytable","name","fred");
  317.      *
  318.      * or write it into your extended class:
  319.      * function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v);  }
  320.      *
  321.      * @param   string  $class class name
  322.      * @param   string  $k     column (or value if using keys)
  323.      * @param   string  $v     value (optional)
  324.      * @access  public
  325.      * @return  object
  326.      */
  327.     function &staticGet($class, $k, $v = null)
  328.     {
  329.         $lclass = strtolower($class);
  330.         global $_DB_DATAOBJECT;
  331.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  332.             DB_DataObject::_loadConfig();
  333.         }
  334.  
  335.         
  336.  
  337.         $key = "$k:$v";
  338.         if ($v === null) {
  339.             $key = $k;
  340.         }
  341.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  342.             DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
  343.         }
  344.         if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
  345.             return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  346.         }
  347.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  348.             DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
  349.         }
  350.  
  351.         $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
  352.         if (PEAR::isError($obj)) {
  353.             DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
  354.             $r = false;
  355.             return $r;
  356.         }
  357.         
  358.         if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
  359.             $_DB_DATAOBJECT['CACHE'][$lclass] = array();
  360.         }
  361.         if (!$obj->get($k,$v)) {
  362.             DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
  363.             
  364.             $r = false;
  365.             return $r;
  366.         }
  367.         $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
  368.         return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  369.     }
  370.  
  371.     /**
  372.      * find results, either normal or crosstable
  373.      *
  374.      * for example
  375.      *
  376.      * $object = new mytable();
  377.      * $object->ID = 1;
  378.      * $object->find();
  379.      *
  380.      *
  381.      * will set $object->N to number of rows, and expects next command to fetch rows
  382.      * will return $object->N
  383.      *
  384.      * @param   boolean $n Fetch first result
  385.      * @access  public
  386.      * @return  mixed (number of rows returned, or true if numRows fetching is not supported)
  387.      */
  388.     function find($n = false)
  389.     {
  390.         global $_DB_DATAOBJECT;
  391.         if (!isset($this->_query)) {
  392.             $this->raiseError(
  393.                 "You cannot do two queries on the same object (copy it before finding)", 
  394.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  395.             return false;
  396.         }
  397.         
  398.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  399.             DB_DataObject::_loadConfig();
  400.         }
  401.  
  402.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  403.             $this->debug($n, "find",1);
  404.         }
  405.         if (!$this->__table) {
  406.             // xdebug can backtrace this!
  407.             trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
  408.         }
  409.         $this->N = 0;
  410.         $query_before = $this->_query;
  411.         $this->_build_condition($this->table()) ;
  412.         
  413.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  414.         $this->_connect();
  415.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  416.        
  417.         /* We are checking for method modifyLimitQuery as it is PEAR DB specific */
  418.         $sql = 'SELECT ' .
  419.             $this->_query['data_select'] . " \n" .
  420.             ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
  421.             $this->_join . " \n" .
  422.             $this->_query['condition'] . " \n" .
  423.             $this->_query['group_by']  . " \n" .
  424.             $this->_query['having']    . " \n" .
  425.             $this->_query['order_by']  . " \n";
  426.         
  427.         if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || 
  428.             ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
  429.             /* PEAR DB specific */
  430.         
  431.             if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  432.                 $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
  433.             }
  434.         } else {
  435.             /* theoretically MDB2! */
  436.             if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  437.                 $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
  438.             }
  439.         }
  440.         
  441.         
  442.         $this->_query($sql);
  443.         
  444.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  445.             $this->debug("CHECK autofetchd $n", "find", 1);
  446.         }
  447.         
  448.         // find(true)
  449.         
  450.         $ret = $this->N;
  451.         if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {     
  452.             // clear up memory if nothing found!?
  453.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  454.         }
  455.         
  456.         if ($n && $this->N > 0 ) {
  457.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  458.                 $this->debug("ABOUT TO AUTOFETCH", "find", 1);
  459.             }
  460.             $fs = $this->fetch();
  461.             // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true)
  462.             // - hence find() also returns false..
  463.             $ret = ($ret === true) ? $fs : $ret;
  464.         }
  465.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  466.             $this->debug("DONE", "find", 1);
  467.         }
  468.         $this->_query = $query_before;
  469.         return $ret;
  470.     }
  471.  
  472.     /**
  473.      * fetches next row into this objects var's
  474.      *
  475.      * returns 1 on success 0 on failure
  476.      *
  477.      *
  478.      *
  479.      * Example
  480.      * $object = new mytable();
  481.      * $object->name = "fred";
  482.      * $object->find();
  483.      * $store = array();
  484.      * while ($object->fetch()) {
  485.      *   echo $this->ID;
  486.      *   $store[] = $object; // builds an array of object lines.
  487.      * }
  488.      *
  489.      * to add features to a fetch
  490.      * function fetch () {
  491.      *    $ret = parent::fetch();
  492.      *    $this->date_formated = date('dmY',$this->date);
  493.      *    return $ret;
  494.      * }
  495.      *
  496.      * @access  public
  497.      * @return  boolean on success
  498.      */
  499.     function fetch()
  500.     {
  501.  
  502.         global $_DB_DATAOBJECT;
  503.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  504.             DB_DataObject::_loadConfig();
  505.         }
  506.         if (empty($this->N)) {
  507.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  508.                 $this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3);
  509.             }
  510.             return false;
  511.         }
  512.         
  513.         if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || 
  514.             !is_object($result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) 
  515.         {
  516.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  517.                 $this->debug('fetched on object after fetch completed (no results found)');
  518.             }
  519.             return false;
  520.         }
  521.         
  522.         
  523.         $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC);
  524.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  525.             $this->debug(serialize($array),"FETCH");
  526.         }
  527.         
  528.         // fetched after last row..
  529.         if ($array === null) {
  530.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  531.                 $t= explode(' ',microtime());
  532.             
  533.                 $this->debug("Last Data Fetch'ed after " . 
  534.                         ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME']  ) . 
  535.                         " seconds",
  536.                     "FETCH", 1);
  537.             }
  538.             // reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
  539.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  540.             
  541.             // we need to keep a copy of resultfields locally so toArray() still works
  542.             // however we dont want to keep it in the global cache..
  543.             
  544.             if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  545.                 $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid];
  546.                 unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  547.             }
  548.             // this is probably end of data!!
  549.             //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
  550.             return false;
  551.         }
  552.         // make sure resultFields is always empty..
  553.         $this->_resultFields = false;
  554.         
  555.         if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  556.             // note: we dont declare this to keep the print_r size down.
  557.             $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
  558.         }
  559.         
  560.         foreach($array as $k=>$v) {
  561.             $kk = str_replace(".", "_", $k);
  562.             $kk = str_replace(" ", "_", $kk);
  563.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  564.                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  565.             }
  566.             $this->$kk = $array[$k];
  567.         }
  568.         
  569.         // set link flag
  570.         $this->_link_loaded=false;
  571.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  572.             $this->debug("{$this->__table} DONE", "fetchrow",2);
  573.         }
  574.         if (isset($this->_query) &&  empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
  575.             unset($this->_query);
  576.         }
  577.         return true;
  578.     }
  579.  
  580.     /**
  581.      * Adds a condition to the WHERE statement, defaults to AND
  582.      *
  583.      * $object->whereAdd(); //reset or cleaer ewhwer
  584.      * $object->whereAdd("ID > 20");
  585.      * $object->whereAdd("age > 20","OR");
  586.      *
  587.      * @param    string  $cond  condition
  588.      * @param    string  $logic optional logic "OR" (defaults to "AND")
  589.      * @access   public
  590.      * @return   string|PEAR::Error - previous condition or Error when invalid args found
  591.      */
  592.     function whereAdd($cond = false, $logic = 'AND')
  593.     {
  594.         
  595.     if (!isset($this->_query)) {
  596.             return $this->raiseError(
  597.                 "You cannot do two queries on the same object (clone it before finding)", 
  598.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  599.         }
  600.         
  601.         if ($cond === false) {
  602.             $r = $this->_query['condition'];
  603.             $this->_query['condition'] = '';
  604.             return preg_replace('/^\s+WHERE\s+/','',$r);
  605.         }
  606.         // check input...= 0 or '   ' == error!
  607.         if (!trim($cond)) {
  608.             return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  609.         }
  610.         $r = $this->_query['condition'];
  611.         if ($this->_query['condition']) {
  612.             $this->_query['condition'] .= " {$logic} ( {$cond} )";
  613.             return $r;
  614.         }
  615.         $this->_query['condition'] = " WHERE ( {$cond} ) ";
  616.         return $r;
  617.     }
  618.  
  619.     /**
  620.      * Adds a order by condition
  621.      *
  622.      * $object->orderBy(); //clears order by
  623.      * $object->orderBy("ID");
  624.      * $object->orderBy("ID,age");
  625.      *
  626.      * @param  string $order  Order
  627.      * @access public
  628.      * @return none|PEAR::Error - invalid args only
  629.      */
  630.     function orderBy($order = false)
  631.     {
  632.         if (!isset($this->_query)) {
  633.             $this->raiseError(
  634.                 "You cannot do two queries on the same object (copy it before finding)", 
  635.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  636.             return false;
  637.         }
  638.         if ($order === false) {
  639.             $this->_query['order_by'] = '';
  640.             return;
  641.         }
  642.         // check input...= 0 or '    ' == error!
  643.         if (!trim($order)) {
  644.             return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  645.         }
  646.         
  647.         if (!$this->_query['order_by']) {
  648.             $this->_query['order_by'] = " ORDER BY {$order} ";
  649.             return;
  650.         }
  651.         $this->_query['order_by'] .= " , {$order}";
  652.     }
  653.  
  654.     /**
  655.      * Adds a group by condition
  656.      *
  657.      * $object->groupBy(); //reset the grouping
  658.      * $object->groupBy("ID DESC");
  659.      * $object->groupBy("ID,age");
  660.      *
  661.      * @param  string  $group  Grouping
  662.      * @access public
  663.      * @return none|PEAR::Error - invalid args only
  664.      */
  665.     function groupBy($group = false)
  666.     {
  667.         if (!isset($this->_query)) {
  668.             $this->raiseError(
  669.                 "You cannot do two queries on the same object (copy it before finding)", 
  670.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  671.             return false;
  672.         }
  673.         if ($group === false) {
  674.             $this->_query['group_by'] = '';
  675.             return;
  676.         }
  677.         // check input...= 0 or '    ' == error!
  678.         if (!trim($group)) {
  679.             return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  680.         }
  681.         
  682.         
  683.         if (!$this->_query['group_by']) {
  684.             $this->_query['group_by'] = " GROUP BY {$group} ";
  685.             return;
  686.         }
  687.         $this->_query['group_by'] .= " , {$group}";
  688.     }
  689.  
  690.     /**
  691.      * Adds a having clause
  692.      *
  693.      * $object->having(); //reset the grouping
  694.      * $object->having("sum(value) > 0 ");
  695.      *
  696.      * @param  string  $having  condition
  697.      * @access public
  698.      * @return none|PEAR::Error - invalid args only
  699.      */
  700.     function having($having = false)
  701.     {
  702.         if (!isset($this->_query)) {
  703.             $this->raiseError(
  704.                 "You cannot do two queries on the same object (copy it before finding)", 
  705.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  706.             return false;
  707.         }
  708.         if ($having === false) {
  709.             $this->_query['having'] = '';
  710.             return;
  711.         }
  712.         // check input...= 0 or '    ' == error!
  713.         if (!trim($having)) {
  714.             return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  715.         }
  716.         
  717.         
  718.         if (!$this->_query['having']) {
  719.             $this->_query['having'] = " HAVING {$having} ";
  720.             return;
  721.         }
  722.         $this->_query['having'] .= " AND {$having}";
  723.     }
  724.  
  725.     /**
  726.      * Sets the Limit
  727.      *
  728.      * $boject->limit(); // clear limit
  729.      * $object->limit(12);
  730.      * $object->limit(12,10);
  731.      *
  732.      * Note this will emit an error on databases other than mysql/postgress
  733.      * as there is no 'clean way' to implement it. - you should consider refering to
  734.      * your database manual to decide how you want to implement it.
  735.      *
  736.      * @param  string $a  limit start (or number), or blank to reset
  737.      * @param  string $b  number
  738.      * @access public
  739.      * @return none|PEAR::Error - invalid args only
  740.      */
  741.     function limit($a = null, $b = null)
  742.     {
  743.         if (!isset($this->_query)) {
  744.             $this->raiseError(
  745.                 "You cannot do two queries on the same object (copy it before finding)", 
  746.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  747.             return false;
  748.         }
  749.         
  750.         if ($a === null) {
  751.            $this->_query['limit_start'] = '';
  752.            $this->_query['limit_count'] = '';
  753.            return;
  754.         }
  755.         // check input...= 0 or '    ' == error!
  756.         if ((!is_int($a) && ((string)((int)$a) !== (string)$a)) 
  757.             || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) {
  758.             return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  759.         }
  760.         global $_DB_DATAOBJECT;
  761.         $this->_connect();
  762.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  763.         
  764.         $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a;
  765.         $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b;
  766.         
  767.     }
  768.  
  769.     /**
  770.      * Adds a select columns
  771.      *
  772.      * $object->selectAdd(); // resets select to nothing!
  773.      * $object->selectAdd("*"); // default select
  774.      * $object->selectAdd("unixtime(DATE) as udate");
  775.      * $object->selectAdd("DATE");
  776.      *
  777.      * to prepend distict:
  778.      * $object->selectAdd('distinct ' . $object->selectAdd());
  779.      *
  780.      * @param  string  $k
  781.      * @access public
  782.      * @return mixed null or old string if you reset it.
  783.      */
  784.     function selectAdd($k = null)
  785.     {
  786.         if (!isset($this->_query)) {
  787.             $this->raiseError(
  788.                 "You cannot do two queries on the same object (copy it before finding)", 
  789.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  790.             return false;
  791.         }
  792.         if ($k === null) {
  793.             $old = $this->_query['data_select'];
  794.             $this->_query['data_select'] = '';
  795.             return $old;
  796.         }
  797.         
  798.         // check input...= 0 or '    ' == error!
  799.         if (!trim($k)) {
  800.             return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  801.         }
  802.         
  803.         if ($this->_query['data_select']) {
  804.             $this->_query['data_select'] .= ', ';
  805.         }
  806.         $this->_query['data_select'] .= " $k ";
  807.     }
  808.     /**
  809.      * Adds multiple Columns or objects to select with formating.
  810.      *
  811.      * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
  812.      *                      // note with null it will also clear the '*' default select
  813.      * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
  814.      * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
  815.      * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
  816.      *                  objectTableName.colnameA as prefix_colnameA
  817.      *
  818.      * @param  array|object|null the array or object to take column names from.
  819.      * @param  string           format in sprintf format (use %s for the colname)
  820.      * @param  string           table name eg. if you have joinAdd'd or send $from as an array.
  821.      * @access public
  822.      * @return void
  823.      */
  824.     function selectAs($from = null,$format = '%s',$tableName=false)
  825.     {
  826.         global $_DB_DATAOBJECT;
  827.         
  828.         if (!isset($this->_query)) {
  829.             $this->raiseError(
  830.                 "You cannot do two queries on the same object (copy it before finding)", 
  831.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  832.             return false;
  833.         }
  834.         
  835.         if ($from === null) {
  836.             // blank the '*' 
  837.             $this->selectAdd();
  838.             $from = $this;
  839.         }
  840.         
  841.         
  842.         $table = $this->__table;
  843.         if (is_object($from)) {
  844.             $table = $from->__table;
  845.             $from = array_keys($from->table());
  846.         }
  847.         
  848.         if ($tableName !== false) {
  849.             $table = $tableName;
  850.         }
  851.         $s = '%s';
  852.         if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
  853.             $this->_connect();
  854.             $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  855.             $s      = $DB->quoteIdentifier($s);
  856.             $format = $DB->quoteIdentifier($format); 
  857.         }
  858.         foreach ($from as $k) {
  859.             $this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k));
  860.         }
  861.         $this->_query['data_select'] .= "\n";
  862.     }
  863.     /**
  864.      * Insert the current objects variables into the database
  865.      *
  866.      * Returns the ID of the inserted element (if auto increment or sequences are used.)
  867.      *
  868.      * for example
  869.      *
  870.      * Designed to be extended
  871.      *
  872.      * $object = new mytable();
  873.      * $object->name = "fred";
  874.      * echo $object->insert();
  875.      *
  876.      * @access public
  877.      * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success
  878.      */
  879.     function insert()
  880.     {
  881.         global $_DB_DATAOBJECT;
  882.         
  883.         // we need to write to the connection (For nextid) - so us the real
  884.         // one not, a copyied on (as ret-by-ref fails with overload!)
  885.         
  886.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  887.             $this->_connect();
  888.         }
  889.         
  890.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  891.         
  892.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  893.          
  894.         $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  895.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  896.             
  897.         if (!$items) {
  898.             $this->raiseError("insert:No table definition for {$this->__table}",
  899.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  900.             return false;
  901.         }
  902.         $options = &$_DB_DATAOBJECT['CONFIG'];
  903.  
  904.  
  905.         $datasaved = 1;
  906.         $leftq     = '';
  907.         $rightq    = '';
  908.      
  909.         $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
  910.                         $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] : 
  911.                         $this->sequenceKey();
  912.         
  913.         $key       = isset($seqKeys[0]) ? $seqKeys[0] : false;
  914.         $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
  915.         $seq       = isset($seqKeys[2]) ? $seqKeys[2] : false;
  916.         
  917.         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
  918.         
  919.          
  920.         // nativeSequences or Sequences..     
  921.  
  922.         // big check for using sequences
  923.         
  924.         if (($key !== false) && !$useNative) { 
  925.         
  926.             if (!$seq) {
  927.                 $keyvalue =  $DB->nextId($this->__table);
  928.             } else {
  929.                 $f = $DB->getOption('seqname_format');
  930.                 $DB->setOption('seqname_format','%s');
  931.                 $keyvalue =  $DB->nextId($seq);
  932.                 $DB->setOption('seqname_format',$f);
  933.             }
  934.             if (PEAR::isError($keyvalue)) {
  935.                 $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  936.                 return false;
  937.             }
  938.             $this->$key = $keyvalue;
  939.         }
  940.  
  941.  
  942.  
  943.         foreach($items as $k => $v) {
  944.             
  945.             // if we are using autoincrement - skip the column...
  946.             if ($key && ($k == $key) && $useNative) {
  947.                 continue;
  948.             }
  949.         
  950.             
  951.             if (!isset($this->$k)) {
  952.                 continue;
  953.             }
  954.             // dont insert data into mysql timestamps 
  955.             // use query() if you really want to do this!!!!
  956.             if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  957.                 continue;
  958.             }
  959.             
  960.             if ($leftq) {
  961.                 $leftq  .= ', ';
  962.                 $rightq .= ', ';
  963.             }
  964.             
  965.             $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ')  : "$k ");
  966.             
  967.             if (is_a($this->$k,'DB_DataObject_Cast')) {
  968.                 $value = $this->$k->toString($v,$DB);
  969.                 if (PEAR::isError($value)) {
  970.                     $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS);
  971.                     return false;
  972.                 }
  973.                 $rightq .=  $value;
  974.                 continue;
  975.             }
  976.             
  977.             
  978.  
  979.             if (is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  980.                 $rightq .= " NULL ";
  981.                 continue;
  982.             }
  983.             // DATE is empty... on a col. that can be null.. 
  984.             // note: this may be usefull for time as well..
  985.             if (!$this->$k && 
  986.                     (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && 
  987.                     !($v & DB_DATAOBJECT_NOTNULL)) {
  988.                     
  989.                 $rightq .= " NULL ";
  990.                 continue;
  991.             }
  992.               
  993.             
  994.             if ($v & DB_DATAOBJECT_STR) {
  995.                 $rightq .= $this->_quote((string) (
  996.                         ($v & DB_DATAOBJECT_BOOL) ? 
  997.                             // this is thanks to the braindead idea of postgres to 
  998.                             // use t/f for boolean.
  999.                             (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :  
  1000.                             $this->$k
  1001.                     )) . " ";
  1002.                 continue;
  1003.             }
  1004.             if (is_numeric($this->$k)) {
  1005.                 $rightq .=" {$this->$k} ";
  1006.                 continue;
  1007.             }
  1008.             /* flag up string values - only at debug level... !!!??? */
  1009.             if (is_object($this->$k) || is_array($this->$k)) {
  1010.                 $this->debug('ODD DATA: ' .$k . ' ' .  print_r($this->$k,true),'ERROR');
  1011.             }
  1012.             
  1013.             // at present we only cast to integers
  1014.             // - V2 may store additional data about float/int
  1015.             $rightq .= ' ' . intval($this->$k) . ' ';
  1016.  
  1017.         }
  1018.         
  1019.         // not sure why we let empty insert here.. - I guess to generate a blank row..
  1020.         
  1021.         
  1022.         if ($leftq || $useNative) {
  1023.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table)    : $this->__table);
  1024.             
  1025.             $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) ");
  1026.  
  1027.             
  1028.             
  1029.             if (PEAR::isError($r)) {
  1030.                 $this->raiseError($r);
  1031.                 return false;
  1032.             }
  1033.             
  1034.             if ($r < 1) {
  1035.                 return 0;
  1036.             }
  1037.             
  1038.             
  1039.             // now do we have an integer key!
  1040.             
  1041.             if ($key && $useNative) {
  1042.                 switch ($dbtype) {
  1043.                     case 'mysql':
  1044.                     case 'mysqli':
  1045.                         $method = "{$dbtype}_insert_id";
  1046.                         $this->$key = $method(
  1047.                             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
  1048.                         );
  1049.                         break;
  1050.                     
  1051.                     case 'mssql':
  1052.                         // note this is not really thread safe - you should wrapp it with 
  1053.                         // transactions = eg.
  1054.                         // $db->query('BEGIN');
  1055.                         // $db->insert();
  1056.                         // $db->query('COMMIT');
  1057.                         
  1058.                         $mssql_key = $DB->getOne("SELECT @@IDENTITY");
  1059.                         if (PEAR::isError($mssql_key)) {
  1060.                             $this->raiseError($r);
  1061.                             return false;
  1062.                         }
  1063.                         $this->$key = $mssql_key;
  1064.                         break; 
  1065.                         
  1066.                     case 'pgsql':
  1067.                         if (!$seq) {
  1068.                             $seq = $DB->getSequenceName($this->__table );
  1069.                         }
  1070.                         $pgsql_key = $DB->getOne("SELECT currval('".$seq . "')"); 
  1071.  
  1072.                         if (PEAR::isError($pgsql_key)) {
  1073.                             $this->raiseError($r);
  1074.                             return false;
  1075.                         }
  1076.                         $this->$key = $pgsql_key;
  1077.                         break;
  1078.                     
  1079.                     case 'ifx':
  1080.                         $this->$key = array_shift (
  1081.                             ifx_fetch_row (
  1082.                                 ifx_query(
  1083.                                     "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1",
  1084.                                     $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection,
  1085.                                     IFX_SCROLL
  1086.                                 ), 
  1087.                                 "FIRST"
  1088.                             )
  1089.                         ); 
  1090.                         break;
  1091.                     
  1092.                 }
  1093.                         
  1094.             }
  1095.  
  1096.             if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
  1097.                 $this->_clear_cache();
  1098.             }
  1099.             if ($key) {
  1100.                 return $this->$key;
  1101.             }
  1102.             return true;
  1103.         }
  1104.         $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  1105.         return false;
  1106.     }
  1107.  
  1108.     /**
  1109.      * Updates  current objects variables into the database
  1110.      * uses the keys() to decide how to update
  1111.      * Returns the  true on success
  1112.      *
  1113.      * for example
  1114.      *
  1115.      * $object = DB_DataObject::factory('mytable');
  1116.      * $object->get("ID",234);
  1117.      * $object->email="testing@test.com";
  1118.      * if(!$object->update())
  1119.      *   echo "UPDATE FAILED";
  1120.      *
  1121.      * to only update changed items :
  1122.      * $dataobject->get(132);
  1123.      * $original = $dataobject; // clone/copy it..
  1124.      * $dataobject->setFrom($_POST);
  1125.      * if ($dataobject->validate()) {
  1126.      *    $dataobject->update($original);
  1127.      * } // otherwise an error...
  1128.      *
  1129.      * performing global updates:
  1130.      * $object = DB_DataObject::factory('mytable');
  1131.      * $object->status = "dead";
  1132.      * $object->whereAdd('age > 150');
  1133.      * $object->update(DB_DATAOBJECT_WHEREADD_ONLY);
  1134.      *
  1135.      * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items.
  1136.      * @access public
  1137.      * @return  int rows affected or false on failure
  1138.      */
  1139.     function update($dataObject = false)
  1140.     {
  1141.         global $_DB_DATAOBJECT;
  1142.         // connect will load the config!
  1143.         $this->_connect();
  1144.         
  1145.         
  1146.         $original_query = isset($this->_query) ? $this->_query : null;
  1147.         
  1148.         $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  1149.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  1150.         
  1151.         // only apply update against sequence key if it is set?????
  1152.         
  1153.         $seq    = $this->sequenceKey();
  1154.         if ($seq[0] !== false) {
  1155.             $keys = array($seq[0]);
  1156.             if (empty($this->{$keys[0]}) && $dataObject !== true) {
  1157.                 $this->raiseError("update: trying to perform an update without 
  1158.                         the key set, and argument to update is not 
  1159.                         DB_DATAOBJECT_WHEREADD_ONLY
  1160.                     ", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1161.                 return false;  
  1162.             }
  1163.         } else {
  1164.             $keys = $this->keys();
  1165.         }
  1166.         
  1167.          
  1168.         if (!$items) {
  1169.             $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1170.             return false;
  1171.         }
  1172.         $datasaved = 1;
  1173.         $settings  = '';
  1174.         $this->_connect();
  1175.         
  1176.         $DB            = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1177.         $dbtype        = $DB->dsn["phptype"];
  1178.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1179.         
  1180.         foreach($items as $k => $v) {
  1181.             if (!isset($this->$k)) {
  1182.                 continue;
  1183.             }
  1184.             // ignore stuff thats 
  1185.           
  1186.             // dont write things that havent changed..
  1187.             if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) {
  1188.                 continue;
  1189.             }
  1190.             
  1191.             // - dont write keys to left.!!!
  1192.             if (in_array($k,$keys)) {
  1193.                 continue;
  1194.             }
  1195.             
  1196.              // dont insert data into mysql timestamps 
  1197.             // use query() if you really want to do this!!!!
  1198.             if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  1199.                 continue;
  1200.             }
  1201.             
  1202.             
  1203.             if ($settings)  {
  1204.                 $settings .= ', ';
  1205.             }
  1206.             
  1207.             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  1208.             
  1209.             if (is_a($this->$k,'DB_DataObject_Cast')) {
  1210.                 $value = $this->$k->toString($v,$DB);
  1211.                 if (PEAR::isError($value)) {
  1212.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  1213.                     return false;
  1214.                 }
  1215.                 $settings .= "$kSql = $value ";
  1216.                 continue;
  1217.             }
  1218.             
  1219.             // special values ... at least null is handled...
  1220.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  1221.                 $settings .= "$kSql = NULL ";
  1222.                 continue;
  1223.             }
  1224.             // DATE is empty... on a col. that can be null.. 
  1225.             // note: this may be usefull for time as well..
  1226.             if (!$this->$k && 
  1227.                     (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && 
  1228.                     !($v & DB_DATAOBJECT_NOTNULL)) {
  1229.                     
  1230.                 $settings .= "$kSql = NULL ";
  1231.                 continue;
  1232.             }
  1233.             
  1234.  
  1235.             if ($v & DB_DATAOBJECT_STR) {
  1236.                 $settings .= "$kSql = ". $this->_quote((string) (
  1237.                         ($v & DB_DATAOBJECT_BOOL) ? 
  1238.                             // this is thanks to the braindead idea of postgres to 
  1239.                             // use t/f for boolean.
  1240.                             (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :  
  1241.                             $this->$k
  1242.                     )) . ' ';
  1243.                 continue;
  1244.             }
  1245.             if (is_numeric($this->$k)) {
  1246.                 $settings .= "$kSql = {$this->$k} ";
  1247.                 continue;
  1248.             }
  1249.             // at present we only cast to integers
  1250.             // - V2 may store additional data about float/int
  1251.             $settings .= "$kSql = " . intval($this->$k) . ' ';
  1252.         }
  1253.  
  1254.         
  1255.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1256.             $this->debug("got keys as ".serialize($keys),3);
  1257.         }
  1258.         if ($dataObject !== true) {
  1259.             $this->_build_condition($items,$keys);
  1260.         } else {
  1261.             // prevent wiping out of data!
  1262.             if (empty($this->_query['condition'])) {
  1263.                  $this->raiseError("update: global table update not available
  1264.                         do \$do->whereAdd('1=1'); if you really want to do that.
  1265.                     ", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1266.                 return false;
  1267.             }
  1268.         }
  1269.         
  1270.         
  1271.         
  1272.         //  echo " $settings, $this->condition ";
  1273.         if ($settings && isset($this->_query) && $this->_query['condition']) {
  1274.             
  1275.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1276.         
  1277.             $r = $this->_query("UPDATE  {$table}  SET {$settings} {$this->_query['condition']} ");
  1278.             
  1279.             // restore original query conditions.
  1280.             $this->_query = $original_query;
  1281.             
  1282.             if (PEAR::isError($r)) {
  1283.                 $this->raiseError($r);
  1284.                 return false;
  1285.             }
  1286.             if ($r < 1) {
  1287.                 return 0;
  1288.             }
  1289.  
  1290.             $this->_clear_cache();
  1291.             return $r;
  1292.         }
  1293.         // restore original query conditions.
  1294.         $this->_query = $original_query;
  1295.         
  1296.         // if you manually specified a dataobject, and there where no changes - then it's ok..
  1297.         if ($dataObject !== false) {
  1298.             return true;
  1299.         }
  1300.         
  1301.         $this->raiseError(
  1302.             "update: No Data specifed for query $settings , {$this->_query['condition']}", 
  1303.             DB_DATAOBJECT_ERROR_NODATA);
  1304.         return false;
  1305.     }
  1306.  
  1307.     /**
  1308.      * Deletes items from table which match current objects variables
  1309.      *
  1310.      * Returns the true on success
  1311.      *
  1312.      * for example
  1313.      *
  1314.      * Designed to be extended
  1315.      *
  1316.      * $object = new mytable();
  1317.      * $object->ID=123;
  1318.      * echo $object->delete(); // builds a conditon
  1319.      *
  1320.      * $object = new mytable();
  1321.      * $object->whereAdd('age > 12');
  1322.      * $object->limit(1);
  1323.      * $object->orderBy('age DESC');
  1324.      * $object->delete(true); // dont use object vars, use the conditions, limit and order.
  1325.      *
  1326.      * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1327.      *             we will build the condition only using the whereAdd's.  Default is to
  1328.      *             build the condition only using the object parameters.
  1329.      *
  1330.      * @access public
  1331.      * @return mixed True on success, false on failure, 0 on no data affected
  1332.      */
  1333.     function delete($useWhere = false)
  1334.     {
  1335.         global $_DB_DATAOBJECT;
  1336.         // connect will load the config!
  1337.         $this->_connect();
  1338.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1339.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1340.         
  1341.         $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : ''); 
  1342.         
  1343.         if (!$useWhere) {
  1344.  
  1345.             $keys = $this->keys();
  1346.             $this->_query = array(); // as it's probably unset!
  1347.             $this->_query['condition'] = ''; // default behaviour not to use where condition
  1348.             $this->_build_condition($this->table(),$keys);
  1349.             // if primary keys are not set then use data from rest of object.
  1350.             if (!$this->_query['condition']) {
  1351.                 $this->_build_condition($this->table(),array(),$keys);
  1352.             }
  1353.             $extra_cond = '';
  1354.         } 
  1355.             
  1356.  
  1357.         // don't delete without a condition
  1358.         if (isset($this->_query) && $this->_query['condition']) {
  1359.         
  1360.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1361.             $sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}";
  1362.             
  1363.             // add limit..
  1364.             
  1365.             if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  1366.                 
  1367.                 if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||  
  1368.                     ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
  1369.                     // pear DB 
  1370.                     $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
  1371.                     
  1372.                 } else {
  1373.                     // MDB2
  1374.                     $DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']);
  1375.                 }
  1376.                     
  1377.             }
  1378.             
  1379.             
  1380.             $r = $this->_query($sql);
  1381.             
  1382.             
  1383.             if (PEAR::isError($r)) {
  1384.                 $this->raiseError($r);
  1385.                 return false;
  1386.             }
  1387.             if ($r < 1) {
  1388.                 return 0;
  1389.             }
  1390.             $this->_clear_cache();
  1391.             return $r;
  1392.         } else {
  1393.             $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  1394.             return false;
  1395.         }
  1396.     }
  1397.  
  1398.     /**
  1399.      * fetches a specific row into this object variables
  1400.      *
  1401.      * Not recommended - better to use fetch()
  1402.      *
  1403.      * Returens true on success
  1404.      *
  1405.      * @param  int   $row  row
  1406.      * @access public
  1407.      * @return boolean true on success
  1408.      */
  1409.     function fetchRow($row = null)
  1410.     {
  1411.         global $_DB_DATAOBJECT;
  1412.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1413.             $this->_loadConfig();
  1414.         }
  1415.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1416.             $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
  1417.         }
  1418.         if (!$this->__table) {
  1419.             $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1420.             return false;
  1421.         }
  1422.         if ($row === null) {
  1423.             $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1424.             return false;
  1425.         }
  1426.         if (!$this->N) {
  1427.             $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA);
  1428.             return false;
  1429.         }
  1430.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1431.             $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
  1432.         }
  1433.  
  1434.  
  1435.         $result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  1436.         $array  = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
  1437.         if (!is_array($array)) {
  1438.             $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
  1439.             return false;
  1440.         }
  1441.  
  1442.         foreach($array as $k => $v) {
  1443.             $kk = str_replace(".", "_", $k);
  1444.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1445.                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  1446.             }
  1447.             $this->$kk = $array[$k];
  1448.         }
  1449.  
  1450.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1451.             $this->debug("{$this->__table} DONE", "fetchrow", 3);
  1452.         }
  1453.         return true;
  1454.     }
  1455.  
  1456.     /**
  1457.      * Find the number of results from a simple query
  1458.      *
  1459.      * for example
  1460.      *
  1461.      * $object = new mytable();
  1462.      * $object->name = "fred";
  1463.      * echo $object->count();
  1464.      * echo $object->count(true);  // dont use object vars.
  1465.      * echo $object->count('distinct mycol');   count distinct mycol.
  1466.      * echo $object->count('distinct mycol',true); // dont use object vars.
  1467.      * echo $object->count('distinct');      // count distinct id (eg. the primary key)
  1468.      *
  1469.      *
  1470.      * @param bool|string  (optional)
  1471.      *                  (true|false => see below not on whereAddonly)
  1472.      *                  (string)
  1473.      *                      "DISTINCT" => does a distinct count on the tables 'key' column
  1474.      *                      otherwise  => normally it counts primary keys - you can use 
  1475.      *                                    this to do things like $do->count('distinct mycol');
  1476.      *                  
  1477.      * @param bool      $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1478.      *                  we will build the condition only using the whereAdd's.  Default is to
  1479.      *                  build the condition using the object parameters as well.
  1480.      *                  
  1481.      * @access public
  1482.      * @return int
  1483.      */
  1484.     function count($countWhat = false,$whereAddOnly = false)
  1485.     {
  1486.         global $_DB_DATAOBJECT;
  1487.         
  1488.         if (is_bool($countWhat)) {
  1489.             $whereAddOnly = $countWhat;
  1490.         }
  1491.         
  1492.         $t = clone($this);
  1493.         
  1494.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1495.         
  1496.         $items   = $t->table();
  1497.         if (!isset($t->_query)) {
  1498.             $this->raiseError(
  1499.                 "You cannot do run count after you have run fetch()", 
  1500.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  1501.             return false;
  1502.         }
  1503.         $this->_connect();
  1504.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1505.        
  1506.  
  1507.         if (!$whereAddOnly && $items)  {
  1508.             $t->_build_condition($items);
  1509.         }
  1510.         $keys = $this->keys();
  1511.  
  1512.         if (!$keys[0] && !is_string($countWhat)) {
  1513.             $this->raiseError(
  1514.                 "You cannot do run count without keys - use \$do->keys('id');", 
  1515.                 DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
  1516.             return false;
  1517.             
  1518.         }
  1519.         $table   = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1520.         $key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]);
  1521.         $as      = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
  1522.         
  1523.         // support distinct on default keys.
  1524.         $countWhat = (strtoupper($countWhat) == 'DISTINCT') ? 
  1525.             "DISTINCT {$table}.{$key_col}" : $countWhat;
  1526.         
  1527.         $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}";
  1528.         
  1529.         $r = $t->_query(
  1530.             "SELECT count({$countWhat}) as $as
  1531.                 FROM $table {$t->_join} {$t->_query['condition']}");
  1532.         if (PEAR::isError($r)) {
  1533.             return false;
  1534.         }
  1535.          
  1536.         $result  = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
  1537.         $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED);
  1538.         // free the results - essential on oracle.
  1539.         $t->free();
  1540.         
  1541.         return (int) $l[0];
  1542.     }
  1543.  
  1544.     /**
  1545.      * sends raw query to database
  1546.      *
  1547.      * Since _query has to be a private 'non overwriteable method', this is a relay
  1548.      *
  1549.      * @param  string  $string  SQL Query
  1550.      * @access public
  1551.      * @return void or DB_Error
  1552.      */
  1553.     function query($string)
  1554.     {
  1555.         return $this->_query($string);
  1556.     }
  1557.  
  1558.  
  1559.     /**
  1560.      * an escape wrapper around DB->escapeSimple()
  1561.      * can be used when adding manual queries or clauses
  1562.      * eg.
  1563.      * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'");
  1564.      *
  1565.      * @param  string  $string  value to be escaped 
  1566.      * @access public
  1567.      * @return string
  1568.      */
  1569.     function escape($string)
  1570.     {
  1571.         global $_DB_DATAOBJECT;
  1572.         $this->_connect();
  1573.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1574.         // mdb2 uses escape...
  1575.         $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
  1576.         return ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string);
  1577.     }
  1578.  
  1579.     /* ==================================================== */
  1580.     /*        Major Private Vars                            */
  1581.     /* ==================================================== */
  1582.  
  1583.     /**
  1584.      * The Database connection dsn (as described in the PEAR DB)
  1585.      * only used really if you are writing a very simple application/test..
  1586.      * try not to use this - it is better stored in configuration files..
  1587.      *
  1588.      * @access  private
  1589.      * @var     string
  1590.      */
  1591.     var $_database_dsn = '';
  1592.  
  1593.     /**
  1594.      * The Database connection id (md5 sum of databasedsn)
  1595.      *
  1596.      * @access  private
  1597.      * @var     string
  1598.      */
  1599.     var $_database_dsn_md5 = '';
  1600.  
  1601.     /**
  1602.      * The Database name
  1603.      * created in __connection
  1604.      *
  1605.      * @access  private
  1606.      * @var  string
  1607.      */
  1608.     var $_database = '';
  1609.  
  1610.     
  1611.     
  1612.     /**
  1613.      * The QUERY rules
  1614.      * This replaces alot of the private variables 
  1615.      * used to build a query, it is unset after find() is run.
  1616.      * 
  1617.      *
  1618.      *
  1619.      * @access  private
  1620.      * @var     array
  1621.      */
  1622.     var $_query = array(
  1623.         'condition'   => '', // the WHERE condition
  1624.         'group_by'    => '', // the GROUP BY condition
  1625.         'order_by'    => '', // the ORDER BY condition
  1626.         'having'      => '', // the HAVING condition
  1627.         'limit_start' => '', // the LIMIT condition
  1628.         'limit_count' => '', // the LIMIT condition
  1629.         'data_select' => '*', // the columns to be SELECTed
  1630.     );
  1631.         
  1632.     
  1633.   
  1634.  
  1635.     /**
  1636.      * Database result id (references global $_DB_DataObject[results]
  1637.      *
  1638.      * @access  private
  1639.      * @var     integer
  1640.      */
  1641.     var $_DB_resultid;
  1642.      
  1643.      /**
  1644.      * ResultFields - on the last call to fetch(), resultfields is sent here,
  1645.      * so we can clean up the memory.
  1646.      *
  1647.      * @access  public
  1648.      * @var     array
  1649.      */
  1650.     var $_resultFields = false; 
  1651.  
  1652.  
  1653.     /* ============================================================== */
  1654.     /*  Table definition layer (started of very private but 'came out'*/
  1655.     /* ============================================================== */
  1656.  
  1657.     /**
  1658.      * Autoload or manually load the table definitions
  1659.      *
  1660.      *
  1661.      * usage :
  1662.      * DB_DataObject::databaseStructure(  'databasename',
  1663.      *                                    parse_ini_file('mydb.ini',true), 
  1664.      *                                    parse_ini_file('mydb.link.ini',true)); 
  1665.      *
  1666.      * obviously you dont have to use ini files.. (just return array similar to ini files..)
  1667.      *  
  1668.      * It should append to the table structure array 
  1669.      *
  1670.      *     
  1671.      * @param optional string  name of database to assign / read
  1672.      * @param optional array   structure of database, and keys
  1673.      * @param optional array  table links
  1674.      *
  1675.      * @access public
  1676.      * @return true or PEAR:error on wrong paramenters.. or false if no file exists..
  1677.      *              or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
  1678.      */
  1679.     function databaseStructure()
  1680.     {
  1681.  
  1682.         global $_DB_DATAOBJECT;
  1683.         
  1684.         // Assignment code 
  1685.         
  1686.         if ($args = func_get_args()) {
  1687.         
  1688.             if (count($args) == 1) {
  1689.                 
  1690.                 // this returns all the tables and their structure..
  1691.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1692.                     $this->debug("Loading Generator as databaseStructure called with args",1);
  1693.                 }
  1694.                 
  1695.                 $x = new DB_DataObject;
  1696.                 $x->_database = $args[0];
  1697.                 $this->_connect();
  1698.                 $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1699.        
  1700.                 $tables = $DB->getListOf('tables');
  1701.                 class_exists('DB_DataObject_Generator') ? '' : 
  1702.                     require_once 'DB/DataObject/Generator.php';
  1703.                     
  1704.                 foreach($tables as $table) {
  1705.                     $y = new DB_DataObject_Generator;
  1706.                     $y->fillTableSchema($x->_database,$table);
  1707.                 }
  1708.                 return $_DB_DATAOBJECT['INI'][$x->_database];            
  1709.             } else {
  1710.         
  1711.                 $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ?
  1712.                     $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1];
  1713.                 
  1714.                 if (isset($args[1])) {
  1715.                     $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ?
  1716.                         $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2];
  1717.                 }
  1718.                 return true;
  1719.             }
  1720.           
  1721.         }
  1722.         
  1723.         
  1724.         
  1725.         if (!$this->_database) {
  1726.             $this->_connect();
  1727.         }
  1728.         
  1729.         // loaded already?
  1730.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
  1731.             
  1732.             // database loaded - but this is table is not available..
  1733.             if (
  1734.                     empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) 
  1735.                     && !empty($_DB_DATAOBJECT['CONFIG']['proxy'])
  1736.                 ) {
  1737.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1738.                     $this->debug("Loading Generator to fetch Schema",1);
  1739.                 }
  1740.                 class_exists('DB_DataObject_Generator') ? '' : 
  1741.                     require_once 'DB/DataObject/Generator.php';
  1742.                     
  1743.                 
  1744.                 $x = new DB_DataObject_Generator;
  1745.                 $x->fillTableSchema($this->_database,$this->__table);
  1746.             }
  1747.             return true;
  1748.         }
  1749.         
  1750.         
  1751.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1752.             DB_DataObject::_loadConfig();
  1753.         }
  1754.         
  1755.         // if you supply this with arguments, then it will take those
  1756.         // as the database and links array...
  1757.          
  1758.         $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
  1759.             array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
  1760.             array() ;
  1761.                  
  1762.         if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  1763.             $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
  1764.                 $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
  1765.                 explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  1766.         }
  1767.                     
  1768.          
  1769.         
  1770.         foreach ($schemas as $ini) {
  1771.              if (file_exists($ini) && is_file($ini)) {
  1772.                 $_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true);
  1773.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { 
  1774.                     if (!is_readable ($ini)) {
  1775.                         $this->debug("ini file is not readable: $ini","databaseStructure",1);
  1776.                     } else {
  1777.                         $this->debug("Loaded ini file: $ini","databaseStructure",1);
  1778.                     }
  1779.                 }
  1780.             } else {
  1781.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1782.                     $this->debug("Missing ini file: $ini","databaseStructure",1);
  1783.                 }
  1784.             }
  1785.              
  1786.         }
  1787.         // now have we loaded the structure.. 
  1788.         
  1789.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1790.             return true;
  1791.         }
  1792.         // - if not try building it..
  1793.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) {
  1794.             class_exists('DB_DataObject_Generator') ? '' : 
  1795.                 require_once 'DB/DataObject/Generator.php';
  1796.                 
  1797.             $x = new DB_DataObject_Generator;
  1798.             $x->fillTableSchema($this->_database,$this->__table);
  1799.             // should this fail!!!???
  1800.             return true;
  1801.         }
  1802.         $this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n".
  1803.                     "in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5);
  1804.         // we have to die here!! - it causes chaos if we dont (including looping forever!)
  1805.         $this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE);
  1806.         return false;
  1807.         
  1808.          
  1809.     }
  1810.  
  1811.  
  1812.  
  1813.  
  1814.     /**
  1815.      * Return or assign the name of the current table
  1816.      *
  1817.      *
  1818.      * @param   string optinal table name to set
  1819.      * @access public
  1820.      * @return string The name of the current table
  1821.      */
  1822.     function tableName()
  1823.     {
  1824.         $args = func_get_args();
  1825.         if (count($args)) {
  1826.             $this->__table = $args[0];
  1827.         }
  1828.         return $this->__table;
  1829.     }
  1830.     
  1831.     /**
  1832.      * Return or assign the name of the current database
  1833.      *
  1834.      * @param   string optional database name to set
  1835.      * @access public
  1836.      * @return string The name of the current database
  1837.      */
  1838.     function database()
  1839.     {
  1840.         $args = func_get_args();
  1841.         if (count($args)) {
  1842.             $this->_database = $args[0];
  1843.         }
  1844.         return $this->_database;
  1845.     }
  1846.   
  1847.     /**
  1848.      * get/set an associative array of table columns
  1849.      *
  1850.      * @access public
  1851.      * @param  array key=>type array
  1852.      * @return array (associative)
  1853.      */
  1854.     function table()
  1855.     {
  1856.         
  1857.         // for temporary storage of database fields..
  1858.         // note this is not declared as we dont want to bloat the print_r output
  1859.         $args = func_get_args();
  1860.         if (count($args)) {
  1861.             $this->_database_fields = $args[0];
  1862.         }
  1863.         if (isset($this->_database_fields)) {
  1864.             return $this->_database_fields;
  1865.         }
  1866.         
  1867.         
  1868.         global $_DB_DATAOBJECT;
  1869.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1870.             $this->_connect();
  1871.         }
  1872.         
  1873.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1874.             return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
  1875.         }
  1876.         
  1877.         $this->databaseStructure();
  1878.  
  1879.         
  1880.         $ret = array();
  1881.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1882.             $ret =  $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
  1883.         }
  1884.         
  1885.         return $ret;
  1886.     }
  1887.  
  1888.     /**
  1889.      * get/set an  array of table primary keys
  1890.      *
  1891.      * set usage: $do->keys('id','code');
  1892.      *
  1893.      * This is defined in the table definition if it gets it wrong,
  1894.      * or you do not want to use ini tables, you can override this.
  1895.      * @param  string optional set the key
  1896.      * @param  *   optional  set more keys
  1897.      * @access private
  1898.      * @return array
  1899.      */
  1900.     function keys()
  1901.     {
  1902.         // for temporary storage of database fields..
  1903.         // note this is not declared as we dont want to bloat the print_r output
  1904.         $args = func_get_args();
  1905.         if (count($args)) {
  1906.             $this->_database_keys = $args;
  1907.         }
  1908.         if (isset($this->_database_keys)) {
  1909.             return $this->_database_keys;
  1910.         }
  1911.         
  1912.         global $_DB_DATAOBJECT;
  1913.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1914.             $this->_connect();
  1915.         }
  1916.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
  1917.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
  1918.         }
  1919.         $this->databaseStructure();
  1920.         
  1921.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
  1922.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
  1923.         }
  1924.         return array();
  1925.     }
  1926.     /**
  1927.      * get/set an  sequence key
  1928.      *
  1929.      * by default it returns the first key from keys()
  1930.      * set usage: $do->sequenceKey('id',true);
  1931.      *
  1932.      * override this to return array(false,false) if table has no real sequence key.
  1933.      *
  1934.      * @param  string  optional the key sequence/autoinc. key
  1935.      * @param  boolean optional use native increment. default false 
  1936.      * @param  false|string optional native sequence name
  1937.      * @access private
  1938.      * @return array (column,use_native,sequence_name)
  1939.      */
  1940.     function sequenceKey()
  1941.     {
  1942.         global $_DB_DATAOBJECT;
  1943.           
  1944.         // call setting
  1945.         if (!$this->_database) {
  1946.             $this->_connect();
  1947.         }
  1948.         
  1949.         if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) {
  1950.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array();
  1951.         }
  1952.  
  1953.         
  1954.         $args = func_get_args();
  1955.         if (count($args)) {
  1956.             $args[1] = isset($args[1]) ? $args[1] : false;
  1957.             $args[2] = isset($args[2]) ? $args[2] : false;
  1958.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = $args;
  1959.         }
  1960.         if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) {
  1961.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table];
  1962.         }
  1963.         // end call setting (eg. $do->sequenceKeys(a,b,c); )
  1964.         
  1965.        
  1966.         
  1967.         
  1968.         $keys = $this->keys();
  1969.         if (!$keys) {
  1970.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] 
  1971.                 = array(false,false,false);
  1972.         }
  1973.  
  1974.  
  1975.         $table =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  1976.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  1977.        
  1978.         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
  1979.         
  1980.         $usekey = $keys[0];
  1981.         
  1982.         
  1983.         
  1984.         $seqname = false;
  1985.         
  1986.         if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
  1987.             $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
  1988.             if (strpos($usekey,':') !== false) {
  1989.                 list($usekey,$seqname) = explode(':',$usekey);
  1990.             }
  1991.         }  
  1992.         
  1993.         
  1994.         // if the key is not an integer - then it's not a sequence or native
  1995.         if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) {
  1996.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,false);
  1997.         }
  1998.         
  1999.         
  2000.         if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
  2001.             $ignore =  $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
  2002.             if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) {
  2003.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  2004.             }
  2005.             if (is_string($ignore)) {
  2006.                 $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore);
  2007.             }
  2008.             if (in_array($this->__table,$ignore)) {
  2009.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  2010.             }
  2011.         }
  2012.         
  2013.         
  2014.         $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
  2015.         
  2016.         // if you are using an old ini file - go back to old behaviour...
  2017.         if (is_numeric($realkeys[$usekey])) {
  2018.             $realkeys[$usekey] = 'N';
  2019.         }
  2020.         
  2021.         // multiple unique primary keys without a native sequence...
  2022.         if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) {
  2023.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  2024.         }
  2025.         // use native sequence keys...
  2026.         // technically postgres native here...
  2027.         // we need to get the new improved tabledata sorted out first.
  2028.         
  2029.         if (    in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) && 
  2030.                 ($table[$usekey] & DB_DATAOBJECT_INT) && 
  2031.                 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
  2032.                 ) {
  2033.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,true,$seqname);
  2034.         }
  2035.         // if not a native autoinc, and we have not assumed all primary keys are sequence
  2036.         if (($realkeys[$usekey] != 'N') && 
  2037.             !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
  2038.             return array(false,false,false);
  2039.         }
  2040.         // I assume it's going to try and be a nextval DB sequence.. (not native)
  2041.         return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,false,$seqname);
  2042.     }
  2043.     
  2044.     
  2045.     
  2046.     /* =========================================================== */
  2047.     /*  Major Private Methods - the core part!              */
  2048.     /* =========================================================== */
  2049.  
  2050.  
  2051.     
  2052.     /**
  2053.      * clear the cache values for this class  - normally done on insert/update etc.
  2054.      *
  2055.      * @access private
  2056.      * @return void
  2057.      */
  2058.     function _clear_cache()
  2059.     {
  2060.         global $_DB_DATAOBJECT;
  2061.         
  2062.         $class = strtolower(get_class($this));
  2063.         
  2064.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2065.             $this->debug("Clearing Cache for ".$class,1);
  2066.         }
  2067.         
  2068.         if (!empty($_DB_DATAOBJECT['CACHE'][$class])) {
  2069.             unset($_DB_DATAOBJECT['CACHE'][$class]);
  2070.         }
  2071.     }
  2072.  
  2073.     
  2074.     /**
  2075.      * backend wrapper for quoting, as MDB2 and DB do it differently...
  2076.      *
  2077.      * @access private
  2078.      * @return string quoted
  2079.      */
  2080.     
  2081.     function _quote($str) 
  2082.     {
  2083.         global $_DB_DATAOBJECT;
  2084.         return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) || 
  2085.                 ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB'))
  2086.             ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str)
  2087.             : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str);
  2088.     }
  2089.     
  2090.     
  2091.     /**
  2092.      * connects to the database
  2093.      *
  2094.      *
  2095.      * TODO: tidy this up - This has grown to support a number of connection options like
  2096.      *  a) dynamic changing of ini file to change which database to connect to
  2097.      *  b) multi data via the table_{$table} = dsn ini option
  2098.      *  c) session based storage.
  2099.      *
  2100.      * @access private
  2101.      * @return true | PEAR::error
  2102.      */
  2103.     function _connect()
  2104.     {
  2105.         global $_DB_DATAOBJECT;
  2106.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2107.             $this->_loadConfig();
  2108.         }
  2109.         // Set database driver for reference 
  2110.         $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
  2111.         // is it already connected ?
  2112.  
  2113.         if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2114.             if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2115.                 return $this->raiseError(
  2116.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
  2117.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
  2118.                 );
  2119.                  
  2120.             }
  2121.  
  2122.             if (!$this->_database) {
  2123.                 $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2124.                 $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
  2125.                 
  2126.                 $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2127.                         ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() 
  2128.                         : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2129.  
  2130.                 
  2131.                 
  2132.                 if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') 
  2133.                     && is_file($this->_database))  {
  2134.                     $this->_database = basename($this->_database);
  2135.                 }
  2136.                 if ($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'ibase')  {
  2137.                     $this->_database = substr(basename($this->_database), 0, -4);
  2138.                 }
  2139.                 
  2140.             }
  2141.             // theoretically we have a md5, it's listed in connections and it's not an error.
  2142.             // so everything is ok!
  2143.             return true;
  2144.             
  2145.         }
  2146.  
  2147.         // it's not currently connected!
  2148.         // try and work out what to use for the dsn !
  2149.  
  2150.         $options= &$_DB_DATAOBJECT['CONFIG'];
  2151.         $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
  2152.         
  2153.         if (!$dsn) {
  2154.             if (!$this->_database) {
  2155.                 $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null;
  2156.             }
  2157.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2158.                 $this->debug("Checking for database database_{$this->_database} in options","CONNECT");
  2159.             }
  2160.             
  2161.             if ($this->_database && !empty($options["database_{$this->_database}"]))  {
  2162.                 
  2163.                 $dsn = $options["database_{$this->_database}"];
  2164.             } else if (!empty($options['database'])) {
  2165.                 $dsn = $options['database'];
  2166.             }
  2167.         }
  2168.         
  2169.         // if still no database...
  2170.         if (!$dsn) {
  2171.             return $this->raiseError(
  2172.                 "No database name / dsn found anywhere",
  2173.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG, PEAR_ERROR_DIE
  2174.             );
  2175.                  
  2176.         }
  2177.         
  2178.         
  2179.         if (is_string($dsn)) {
  2180.             $this->_database_dsn_md5 = md5($dsn);
  2181.         } else {
  2182.             /// support array based dsn's
  2183.             $this->_database_dsn_md5 = md5(serialize($dsn));
  2184.         }
  2185.  
  2186.         if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2187.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2188.                 $this->debug("USING CACHED CONNECTION", "CONNECT",3);
  2189.             }
  2190.             if (!$this->_database) {
  2191.  
  2192.                 $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
  2193.                 $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2194.                         ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() 
  2195.                         : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2196.                 
  2197.                 if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') 
  2198.                     && is_file($this->_database)) 
  2199.                 {
  2200.                     $this->_database = basename($this->_database);
  2201.                 }
  2202.             }
  2203.             return true;
  2204.         }
  2205.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2206.             $this->debug("NEW CONNECTION", "CONNECT",3);
  2207.             /* actualy make a connection */
  2208.             $this->debug(print_r($dsn,true) ." {$this->_database_dsn_md5}", "CONNECT",3);
  2209.         }
  2210.         
  2211.         // Note this is verbose deliberatly! 
  2212.         
  2213.         if ($db_driver == 'DB') {
  2214.             
  2215.             /* PEAR DB connect */
  2216.             
  2217.             // this allows the setings of compatibility on DB 
  2218.             $db_options = PEAR::getStaticProperty('DB','options');
  2219.             require_once 'DB.php';
  2220.             if ($db_options) {
  2221.                 $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options);
  2222.             } else {
  2223.                 $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn);
  2224.             }
  2225.             
  2226.         } else {
  2227.             /* assumption is MDB2 */
  2228.             require_once 'MDB2.php';
  2229.             // this allows the setings of compatibility on MDB2 
  2230.             $db_options = PEAR::getStaticProperty('MDB2','options');
  2231.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn,$db_options);
  2232.             
  2233.         }
  2234.         
  2235.         
  2236.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2237.             $this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5);
  2238.         }
  2239.         if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2240.             $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED",5);
  2241.             return $this->raiseError(
  2242.                     "Connect failed, turn on debugging to 5 see why",
  2243.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
  2244.             );
  2245.  
  2246.         }
  2247.  
  2248.         if (!$this->_database) {
  2249.             $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
  2250.             
  2251.             $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2252.                     ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() 
  2253.                     : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2254.  
  2255.  
  2256.             if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') 
  2257.                 && is_file($this->_database)) 
  2258.             {
  2259.                 $this->_database = basename($this->_database);
  2260.             }
  2261.         }
  2262.         
  2263.         // Oracle need to optimize for portibility - not sure exactly what this does though :)
  2264.         $c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2265.          
  2266.         return true;
  2267.     }
  2268.  
  2269.     /**
  2270.      * sends query to database - this is the private one that must work 
  2271.      *   - internal functions use this rather than $this->query()
  2272.      *
  2273.      * @param  string  $string
  2274.      * @access private
  2275.      * @return mixed none or PEAR_Error
  2276.      */
  2277.     function _query($string)
  2278.     {
  2279.         global $_DB_DATAOBJECT;
  2280.         $this->_connect();
  2281.         
  2282.  
  2283.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2284.  
  2285.         $options = &$_DB_DATAOBJECT['CONFIG'];
  2286.         
  2287.         $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 
  2288.                     'DB':  $_DB_DATAOBJECT['CONFIG']['db_driver'];
  2289.         
  2290.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2291.             $this->debug($string,$log="QUERY");
  2292.             
  2293.         }
  2294.         
  2295.         if (strtoupper($string) == 'BEGIN') {
  2296.             if ($_DB_driver == 'DB') {
  2297.                 $DB->autoCommit(false);
  2298.             } else {
  2299.                 $DB->beginTransaction();
  2300.             }
  2301.             // db backend adds begin anyway from now on..
  2302.             return true;
  2303.         }
  2304.         if (strtoupper($string) == 'COMMIT') {
  2305.             $res = $DB->commit();
  2306.             if ($_DB_driver == 'DB') {
  2307.                 $DB->autoCommit(true);
  2308.             }
  2309.             return $res;
  2310.         }
  2311.         
  2312.         if (strtoupper($string) == 'ROLLBACK') {
  2313.             $DB->rollback();
  2314.             if ($_DB_driver == 'DB') {
  2315.                 $DB->autoCommit(true);
  2316.             }
  2317.             return true;
  2318.         }
  2319.         
  2320.  
  2321.         if (!empty($options['debug_ignore_updates']) &&
  2322.             (strtolower(substr(trim($string), 0, 6)) != 'select') &&
  2323.             (strtolower(substr(trim($string), 0, 4)) != 'show') &&
  2324.             (strtolower(substr(trim($string), 0, 8)) != 'describe')) {
  2325.  
  2326.             $this->debug('Disabling Update as you are in debug mode');
  2327.             return $this->raiseError("Disabling Update as you are in debug mode", null) ;
  2328.  
  2329.         }
  2330.         //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) {
  2331.             // this will only work when PEAR:DB supports it.
  2332.             //$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2);
  2333.         //}
  2334.         
  2335.         // some sim
  2336.         $t= explode(' ',microtime());
  2337.         $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1];
  2338.          
  2339.         
  2340.         if ($_DB_driver == 'DB') {
  2341.             $result = $DB->query($string);
  2342.         } else {
  2343.             switch (strtolower(substr(trim($string),0,6))) {
  2344.             
  2345.                 case 'insert':
  2346.                 case 'update':
  2347.                 case 'delete':
  2348.                     $result = $DB->exec($string);
  2349.                     break;
  2350.                     
  2351.                 default:
  2352.                     $result = $DB->query($string);
  2353.                     break;
  2354.             }
  2355.         }
  2356.         
  2357.        
  2358.  
  2359.         if (is_a($result,'PEAR_Error')) {
  2360.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { 
  2361.                 $this->debug($result->toString(), "Query Error",1 );
  2362.             }
  2363.             return $this->raiseError($result);
  2364.         }
  2365.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2366.             $t= explode(' ',microtime());
  2367.             $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1];
  2368.             $this->debug('QUERY DONE IN  '.($t[0]+$t[1]-$time)." seconds", 'query',1);
  2369.         }
  2370.         switch (strtolower(substr(trim($string),0,6))) {
  2371.             case 'insert':
  2372.             case 'update':
  2373.             case 'delete':
  2374.                 if ($_DB_driver == 'DB') {
  2375.                     // pear DB specific
  2376.                     return $DB->affectedRows(); 
  2377.                 }
  2378.                 return $result;
  2379.         }
  2380.         if (is_object($result)) {
  2381.             // lets hope that copying the result object is OK!
  2382.             
  2383.             $_DB_resultid  = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++;
  2384.             $_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result; 
  2385.             $this->_DB_resultid = $_DB_resultid;
  2386.         }
  2387.         $this->N = 0;
  2388.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2389.             $this->debug(serialize($result), 'RESULT',5);
  2390.         }
  2391.         if (method_exists($result, 'numrows')) {
  2392.             if ($_DB_driver == 'DB') {
  2393.                 $DB->expectError(DB_ERROR_UNSUPPORTED);
  2394.             } else {
  2395.                 $DB->expectError(MDB2_ERROR_UNSUPPORTED);
  2396.             }
  2397.             $this->N = $result->numrows();
  2398.             if (is_a($this->N,'PEAR_Error')) {
  2399.                 $this->N = true;
  2400.             }
  2401.             $DB->popExpect();
  2402.         }
  2403.     }
  2404.  
  2405.     /**
  2406.      * Builds the WHERE based on the values of of this object
  2407.      *
  2408.      * @param   mixed   $keys
  2409.      * @param   array   $filter (used by update to only uses keys in this filter list).
  2410.      * @param   array   $negative_filter (used by delete to prevent deleting using the keys mentioned..)
  2411.      * @access  private
  2412.      * @return  string
  2413.      */
  2414.     function _build_condition($keys, $filter = array(),$negative_filter=array())
  2415.     {
  2416.         global $_DB_DATAOBJECT;
  2417.         $this->_connect();
  2418.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2419.        
  2420.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  2421.         // if we dont have query vars.. - reset them.
  2422.         if (!isset($this->_query)) {
  2423.             $x = new DB_DataObject;
  2424.             $this->_query= $x->_query;
  2425.         }
  2426.  
  2427.         foreach($keys as $k => $v) {
  2428.             // index keys is an indexed array
  2429.             /* these filter checks are a bit suspicious..
  2430.                 - need to check that update really wants to work this way */
  2431.  
  2432.             if ($filter) {
  2433.                 if (!in_array($k, $filter)) {
  2434.                     continue;
  2435.                 }
  2436.             }
  2437.             if ($negative_filter) {
  2438.                 if (in_array($k, $negative_filter)) {
  2439.                     continue;
  2440.                 }
  2441.             }
  2442.             if (!isset($this->$k)) {
  2443.                 continue;
  2444.             }
  2445.             
  2446.             $kSql = $quoteIdentifiers 
  2447.                 ? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) )  
  2448.                 : "{$this->__table}.{$k}";
  2449.              
  2450.              
  2451.             
  2452.             if (is_a($this->$k,'DB_DataObject_Cast')) {
  2453.                 $dbtype = $DB->dsn["phptype"];
  2454.                 $value = $this->$k->toString($v,$DB);
  2455.                 if (PEAR::isError($value)) {
  2456.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  2457.                     return false;
  2458.                 }
  2459.                 if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  2460.                     $this->whereAdd(" $kSql IS NULL");
  2461.                     continue;
  2462.                 }
  2463.                 $this->whereAdd(" $kSql = $value");
  2464.                 continue;
  2465.             }
  2466.             
  2467.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  2468.                 $this->whereAdd(" $kSql  IS NULL");
  2469.                 continue;
  2470.             }
  2471.             
  2472.  
  2473.             if ($v & DB_DATAOBJECT_STR) {
  2474.                 $this->whereAdd(" $kSql  = " . $this->_quote((string) (
  2475.                         ($v & DB_DATAOBJECT_BOOL) ? 
  2476.                             // this is thanks to the braindead idea of postgres to 
  2477.                             // use t/f for boolean.
  2478.                             (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :  
  2479.                             $this->$k
  2480.                     )) );
  2481.                 continue;
  2482.             }
  2483.             if (is_numeric($this->$k)) {
  2484.                 $this->whereAdd(" $kSql = {$this->$k}");
  2485.                 continue;
  2486.             }
  2487.             /* this is probably an error condition! */
  2488.             $this->whereAdd(" $kSql = ".intval($this->$k));
  2489.         }
  2490.     }
  2491.  
  2492.     /**
  2493.      * autoload Class relating to a table
  2494.      * (depreciated - use ::factory)
  2495.      *
  2496.      * @param  string  $table  table
  2497.      * @access private
  2498.      * @return string classname on Success
  2499.      */
  2500.     function staticAutoloadTable($table)
  2501.     {
  2502.         global $_DB_DATAOBJECT;
  2503.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2504.             DB_DataObject::_loadConfig();
  2505.         }
  2506.         $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
  2507.             $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
  2508.         $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2509.         
  2510.         $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
  2511.         $class = $ce ? $class  : DB_DataObject::_autoloadClass($class);
  2512.         return $class;
  2513.     }
  2514.     
  2515.     
  2516.      /**
  2517.      * classic factory method for loading a table class
  2518.      * usage: $do = DB_DataObject::factory('person')
  2519.      * WARNING - this may emit a include error if the file does not exist..
  2520.      * use @ to silence it (if you are sure it is acceptable)
  2521.      * eg. $do = @DB_DataObject::factory('person')
  2522.      *
  2523.      * table name will eventually be databasename/table
  2524.      * - and allow modular dataobjects to be written..
  2525.      * (this also helps proxy creation)
  2526.      *
  2527.      *
  2528.      * @param  string  $table  tablename (use blank to create a new instance of the same class.)
  2529.      * @access private
  2530.      * @return DataObject|PEAR_Error 
  2531.      */
  2532.     
  2533.     
  2534.  
  2535.     function factory($table = '') {
  2536.         global $_DB_DATAOBJECT;
  2537.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2538.             DB_DataObject::_loadConfig();
  2539.         }
  2540.         
  2541.         if ($table === '') {
  2542.             if (is_a($this,'DB_DataObject') && strlen($this->__table)) {
  2543.                 $table = $this->__table;
  2544.             } else {
  2545.                 return DB_DataObject::raiseError(
  2546.                     "factory did not recieve a table name",
  2547.                     DB_DATAOBJECT_ERROR_INVALIDARGS);
  2548.             }
  2549.         }
  2550.         
  2551.         
  2552.         $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
  2553.             $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
  2554.         $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2555.         
  2556.         $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
  2557.         $class = $ce ? $class  : DB_DataObject::_autoloadClass($class);
  2558.         
  2559.         // proxy = full|light
  2560.         if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { 
  2561.             $proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy'];
  2562.             class_exists('DB_DataObject_Generator') ? '' : 
  2563.                     require_once 'DB/DataObject/Generator.php';
  2564.             
  2565.             $d = new DB_DataObject;
  2566.            
  2567.             $d->__table = $table;
  2568.             if (is_a($ret = $d->_connect(), 'PEAR_Error')) {
  2569.                 return $ret;
  2570.             }
  2571.             
  2572.             $x = new DB_DataObject_Generator;
  2573.             return $x->$proxyMethod( $d->_database, $table);
  2574.         }
  2575.         
  2576.         if (!$class) {
  2577.             return DB_DataObject::raiseError(
  2578.                 "factory could not find class $class from $table",
  2579.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2580.         }
  2581.  
  2582.         return new $class;
  2583.     }
  2584.     /**
  2585.      * autoload Class
  2586.      *
  2587.      * @param  string  $class  Class
  2588.      * @access private
  2589.      * @return string classname on Success
  2590.      */
  2591.     function _autoloadClass($class)
  2592.     {
  2593.         global $_DB_DATAOBJECT;
  2594.         
  2595.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2596.             DB_DataObject::_loadConfig();
  2597.         }
  2598.         $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? 
  2599.                 '' : $_DB_DATAOBJECT['CONFIG']['class_prefix'];
  2600.                 
  2601.         $table   = substr($class,strlen($class_prefix));
  2602.  
  2603.         // only include the file if it exists - and barf badly if it has parse errors :)
  2604.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
  2605.             return false;
  2606.         }
  2607.         
  2608.         
  2609.         if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) {
  2610.             $file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)));
  2611.         } else {
  2612.             $file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
  2613.         }
  2614.         
  2615.         if (!file_exists($file)) {
  2616.             $found = false;
  2617.             foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) {
  2618.                 if (file_exists("$p/$file")) {
  2619.                     $file = "$p/$file";
  2620.                     $found = true;
  2621.                     break;
  2622.                 }
  2623.             }
  2624.             if (!$found) {
  2625.                 DB_DataObject::raiseError(
  2626.                     "autoload:Could not find class {$class} using class_location value", 
  2627.                     DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2628.                 return false;
  2629.             }
  2630.         }
  2631.         
  2632.         include_once $file;
  2633.         
  2634.         
  2635.         $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
  2636.         
  2637.         if (!$ce) {
  2638.             DB_DataObject::raiseError(
  2639.                 "autoload:Could not autoload {$class}", 
  2640.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2641.             return false;
  2642.         }
  2643.         return $class;
  2644.     }
  2645.     
  2646.     
  2647.     
  2648.     /**
  2649.      * Have the links been loaded?
  2650.      * if they have it contains a array of those variables.
  2651.      *
  2652.      * @access  private
  2653.      * @var     boolean | array
  2654.      */
  2655.     var $_link_loaded = false;
  2656.     
  2657.     /**
  2658.     * Get the links associate array  as defined by the links.ini file.
  2659.     * 
  2660.     *
  2661.     * Experimental... - 
  2662.     * Should look a bit like
  2663.     *       [local_col_name] => "related_tablename:related_col_name"
  2664.     * 
  2665.     * 
  2666.     * @return   array|null    
  2667.     *           array       = if there are links defined for this table.
  2668.     *           empty array - if there is a links.ini file, but no links on this table
  2669.     *           null        - if no links.ini exists for this database (hence try auto_links).
  2670.     * @access   public
  2671.     * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
  2672.     */
  2673.     
  2674.     function links()
  2675.     {
  2676.         global $_DB_DATAOBJECT;
  2677.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2678.             $this->_loadConfig();
  2679.         }
  2680.         // have to connect.. -> otherwise things break later.
  2681.         $this->_connect();
  2682.         
  2683.         if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
  2684.             return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
  2685.         }
  2686.         
  2687.         
  2688.         
  2689.         
  2690.         
  2691.         // attempt to load links file here..
  2692.         
  2693.         if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
  2694.             $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
  2695.                 array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
  2696.                 array() ;
  2697.                      
  2698.             if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  2699.                 $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
  2700.                     $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
  2701.                     explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  2702.             }
  2703.                         
  2704.              
  2705.             
  2706.             foreach ($schemas as $ini) {
  2707.                 
  2708.                 $links =
  2709.                     isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ?
  2710.                         $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
  2711.                         str_replace('.ini','.links.ini',$ini);
  2712.         
  2713.                 if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) {
  2714.                     /* not sure why $links = ... here  - TODO check if that works */
  2715.                     $_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true);
  2716.                     if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2717.                         $this->debug("Loaded links.ini file: $links","links",1);
  2718.                     }
  2719.                 } else {
  2720.                     if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2721.                         $this->debug("Missing links.ini file: $links","links",1);
  2722.                     }
  2723.                 }
  2724.             }
  2725.         }
  2726.         
  2727.         
  2728.         // if there is no link data at all on the file!
  2729.         // we return null.
  2730.         if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
  2731.             return null;
  2732.         }
  2733.         
  2734.         if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
  2735.             return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
  2736.         }
  2737.         
  2738.         return array();
  2739.     }
  2740.     /**
  2741.      * load related objects
  2742.      *
  2743.      * There are two ways to use this, one is to set up a <dbname>.links.ini file
  2744.      * into a static property named <dbname>.links and specifies the table joins,
  2745.      * the other highly dependent on naming columns 'correctly' :)
  2746.      * using colname = xxxxx_yyyyyy
  2747.      * xxxxxx = related table; (yyyyy = user defined..)
  2748.      * looks up table xxxxx, for value id=$this->xxxxx
  2749.      * stores it in $this->_xxxxx_yyyyy
  2750.      * you can change what object vars the links are stored in by 
  2751.      * changeing the format parameter
  2752.      *
  2753.      *
  2754.      * @param  string format (default _%s) where %s is the table name.
  2755.      * @author Tim White <tim@cyface.com>
  2756.      * @access public
  2757.      * @return boolean , true on success
  2758.      */
  2759.     function getLinks($format = '_%s')
  2760.     {
  2761.          
  2762.         // get table will load the options.
  2763.         if ($this->_link_loaded) {
  2764.             return true;
  2765.         }
  2766.         $this->_link_loaded = false;
  2767.         $cols  = $this->table();
  2768.         $links = $this->links();
  2769.          
  2770.         $loaded = array();
  2771.         
  2772.         if ($links) {   
  2773.             foreach($links as $key => $match) {
  2774.                 list($table,$link) = explode(':', $match);
  2775.                 $k = sprintf($format, str_replace('.', '_', $key));
  2776.                 // makes sure that '.' is the end of the key;
  2777.                 if ($p = strpos($key,'.')) {
  2778.                       $key = substr($key, 0, $p);
  2779.                 }
  2780.                 
  2781.                 $this->$k = $this->getLink($key, $table, $link);
  2782.                 
  2783.                 if (is_object($this->$k)) {
  2784.                     $loaded[] = $k; 
  2785.                 }
  2786.             }
  2787.             $this->_link_loaded = $loaded;
  2788.             return true;
  2789.         }
  2790.         // this is the autonaming stuff..
  2791.         // it sends the column name down to getLink and lets that sort it out..
  2792.         // if there is a links file then it is not used!
  2793.         // IT IS DEPRECIATED!!!! - USE 
  2794.         if (!is_null($links)) {    
  2795.             return false;
  2796.         }
  2797.         
  2798.         
  2799.         foreach (array_keys($cols) as $key) {
  2800.             if (!($p = strpos($key, '_'))) {
  2801.                 continue;
  2802.             }
  2803.             // does the table exist.
  2804.             $k =sprintf($format, $key);
  2805.             $this->$k = $this->getLink($key);
  2806.             if (is_object($this->$k)) {
  2807.                 $loaded[] = $k; 
  2808.             }
  2809.         }
  2810.         $this->_link_loaded = $loaded;
  2811.         return true;
  2812.     }
  2813.  
  2814.     /**
  2815.      * return name from related object
  2816.      *
  2817.      * There are two ways to use this, one is to set up a <dbname>.links.ini file
  2818.      * into a static property named <dbname>.links and specifies the table joins,
  2819.      * the other is highly dependant on naming columns 'correctly' :)
  2820.      *
  2821.      * NOTE: the naming convention is depreciated!!! - use links.ini
  2822.      *
  2823.      * using colname = xxxxx_yyyyyy
  2824.      * xxxxxx = related table; (yyyyy = user defined..)
  2825.      * looks up table xxxxx, for value id=$this->xxxxx
  2826.      * stores it in $this->_xxxxx_yyyyy
  2827.      *
  2828.      * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
  2829.      *
  2830.      *
  2831.      * @param string $row    either row or row.xxxxx
  2832.      * @param string $table  name of table to look up value in
  2833.      * @param string $link   name of column in other table to match
  2834.      * @author Tim White <tim@cyface.com>
  2835.      * @access public
  2836.      * @return mixed object on success
  2837.      */
  2838.     function getLink($row, $table = null, $link = false)
  2839.     {
  2840.         
  2841.         
  2842.         // GUESS THE LINKED TABLE.. (if found - recursevly call self)
  2843.         
  2844.         if ($table === null) {
  2845.             $links = $this->links();
  2846.             
  2847.             if (is_array($links)) {
  2848.             
  2849.                 if ($links[$row]) {
  2850.                     list($table,$link) = explode(':', $links[$row]);
  2851.                     if ($p = strpos($row,".")) {
  2852.                         $row = substr($row,0,$p);
  2853.                     }
  2854.                     return $this->getLink($row,$table,$link);
  2855.                     
  2856.                 } 
  2857.                 
  2858.                 $this->raiseError(
  2859.                     "getLink: $row is not defined as a link (normally this is ok)", 
  2860.                     DB_DATAOBJECT_ERROR_NODATA);
  2861.                     
  2862.                 $r = false;
  2863.                 return $r;// technically a possible error condition?
  2864.  
  2865.             }  
  2866.             // use the old _ method - this shouldnt happen if called via getLinks()
  2867.             if (!($p = strpos($row, '_'))) {
  2868.                 $r = null;
  2869.                 return $r; 
  2870.             }
  2871.             $table = substr($row, 0, $p);
  2872.             return $this->getLink($row, $table);
  2873.             
  2874.  
  2875.         }
  2876.         
  2877.         
  2878.         
  2879.         if (!isset($this->$row)) {
  2880.             $this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
  2881.             return false;
  2882.         }
  2883.         
  2884.         // check to see if we know anything about this table..
  2885.         
  2886.         $obj = $this->factory($table);
  2887.         
  2888.         if (!is_a($obj,'DB_DataObject')) {
  2889.             $this->raiseError(
  2890.                 "getLink:Could not find class for row $row, table $table", 
  2891.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2892.             return false;
  2893.         }
  2894.         if ($link) {
  2895.             if ($obj->get($link, $this->$row)) {
  2896.                 $obj->free();
  2897.                 return $obj;
  2898.             } 
  2899.             return  false;
  2900.         }
  2901.         
  2902.         if ($obj->get($this->$row)) {
  2903.             $obj->free();
  2904.             return $obj;
  2905.         }
  2906.         return false;
  2907.         
  2908.     }
  2909.  
  2910.     /**
  2911.      * IS THIS SUPPORTED/USED ANYMORE???? 
  2912.      *return a list of options for a linked table
  2913.      *
  2914.      * This is highly dependant on naming columns 'correctly' :)
  2915.      * using colname = xxxxx_yyyyyy
  2916.      * xxxxxx = related table; (yyyyy = user defined..)
  2917.      * looks up table xxxxx, for value id=$this->xxxxx
  2918.      * stores it in $this->_xxxxx_yyyyy
  2919.      *
  2920.      * @access public
  2921.      * @return array of results (empty array on failure)
  2922.      */
  2923.     function &getLinkArray($row, $table = null)
  2924.     {
  2925.         
  2926.         $ret = array();
  2927.         if (!$table) {
  2928.             $links = $this->links();
  2929.             
  2930.             if (is_array($links)) {
  2931.                 if (!isset($links[$row])) {
  2932.                     // failed..
  2933.                     return $ret;
  2934.                 }
  2935.                 list($table,$link) = explode(':',$links[$row]);
  2936.             } else {
  2937.                 if (!($p = strpos($row,'_'))) {
  2938.                     return $ret;
  2939.                 }
  2940.                 $table = substr($row,0,$p);
  2941.             }
  2942.         }
  2943.         
  2944.         $c  = $this->factory($table);
  2945.         
  2946.         if (!is_a($c,'DB_DataObject')) {
  2947.             $this->raiseError(
  2948.                 "getLinkArray:Could not find class for row $row, table $table", 
  2949.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG
  2950.             );
  2951.             return $ret;
  2952.         }
  2953.  
  2954.         // if the user defined method list exists - use it...
  2955.         if (method_exists($c, 'listFind')) {
  2956.             $c->listFind($this->id);
  2957.         } else {
  2958.             $c->find();
  2959.         }
  2960.         while ($c->fetch()) {
  2961.             $ret[] = $c;
  2962.         }
  2963.         return $ret;
  2964.     }
  2965.  
  2966.     /**
  2967.      * The JOIN condition
  2968.      *
  2969.      * @access  private
  2970.      * @var     string
  2971.      */
  2972.     var $_join = '';
  2973.  
  2974.     /**
  2975.      * joinAdd - adds another dataobject to this, building a joined query.
  2976.      *
  2977.      * example (requires links.ini to be set up correctly)
  2978.      * // get all the images for product 24
  2979.      * $i = new DataObject_Image();
  2980.      * $pi = new DataObjects_Product_image();
  2981.      * $pi->product_id = 24; // set the product id to 24
  2982.      * $i->joinAdd($pi); // add the product_image connectoin
  2983.      * $i->find();
  2984.      * while ($i->fetch()) {
  2985.      *     // do stuff
  2986.      * }
  2987.      * // an example with 2 joins
  2988.      * // get all the images linked with products or productgroups
  2989.      * $i = new DataObject_Image();
  2990.      * $pi = new DataObject_Product_image();
  2991.      * $pgi = new DataObject_Productgroup_image();
  2992.      * $i->joinAdd($pi);
  2993.      * $i->joinAdd($pgi);
  2994.      * $i->find();
  2995.      * while ($i->fetch()) {
  2996.      *     // do stuff
  2997.      * }
  2998.      *
  2999.      *
  3000.      * @param    optional $obj       object |array    the joining object (no value resets the join)
  3001.      *                                          If you use an array here it should be in the format:
  3002.      *                                          array('local_column','remotetable:remote_column');
  3003.      *                                          if remotetable does not have a definition, you should
  3004.      *                                          use @ to hide the include error message..
  3005.      *                                      
  3006.      *
  3007.      * @param    optional $joinType  string     'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates 
  3008.      *                                          just select ... from a,b,c with no join and 
  3009.      *                                          links are added as where items.
  3010.      *
  3011.      * @param    optional $joinAs    string     if you want to select the table as anther name
  3012.      *                                          useful when you want to select multiple columsn
  3013.      *                                          from a secondary table.
  3014.      
  3015.      * @param    optional $joinCol   string     The column on This objects table to match (needed
  3016.      *                                          if this table links to the child object in 
  3017.      *                                          multiple places eg.
  3018.      *                                          user->friend (is a link to another user)
  3019.      *                                          user->mother (is a link to another user..)
  3020.      *
  3021.      * @return   none
  3022.      * @access   public
  3023.      * @author   Stijn de Reede      <sjr@gmx.co.uk>
  3024.      */
  3025.     function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false)
  3026.     {
  3027.         global $_DB_DATAOBJECT;
  3028.         if ($obj === false) {
  3029.             $this->_join = '';
  3030.             return;
  3031.         }
  3032.         
  3033.         // support for array as first argument 
  3034.         // this assumes that you dont have a links.ini for the specified table.
  3035.         // and it doesnt exist as am extended dataobject!! - experimental.
  3036.         
  3037.         $ofield = false; // object field
  3038.         $tfield = false; // this field
  3039.         $toTable = false;
  3040.         if (is_array($obj)) {
  3041.             $tfield = $obj[0];
  3042.             list($toTable,$ofield) = explode(':',$obj[1]);
  3043.             $obj = DB_DataObject::factory($toTable);
  3044.             
  3045.             if (!$obj || is_a($obj,'PEAR_Error')) {
  3046.                 $obj = new DB_DataObject;
  3047.                 $obj->__table = $toTable;
  3048.             }
  3049.             $obj->_connect();
  3050.             // set the table items to nothing.. - eg. do not try and match
  3051.             // things in the child table...???
  3052.             $items = array();
  3053.         }
  3054.         
  3055.         if (!is_object($obj) || !is_a($obj,'DB_DataObject')) {
  3056.             return $this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
  3057.         }
  3058.         /*  make sure $this->_database is set.  */
  3059.         $this->_connect();
  3060.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  3061.        
  3062.  
  3063.         
  3064.         
  3065.          /* look up the links for obj table */
  3066.         //print_r($obj->links());
  3067.         if (!$ofield && ($olinks = $obj->links())) {
  3068.             
  3069.             foreach ($olinks as $k => $v) {
  3070.                 /* link contains {this column} = {linked table}:{linked column} */
  3071.                 $ar = explode(':', $v);
  3072.                 
  3073.                 // Feature Request #4266 - Allow joins with multiple keys
  3074.                 
  3075.                 $links_key_array = strpos($k,',');
  3076.                 if ($links_key_array !== false) {
  3077.                     $k = explode(',', $k);
  3078.                 }
  3079.                 
  3080.                 $ar_array = strpos($ar[1],',');
  3081.                 if ($ar_array !== false) {
  3082.                     $ar[1] = explode(',', $ar[1]);
  3083.                 }
  3084.              
  3085.                 if ($ar[0] == $this->__table) {
  3086.                     
  3087.                     // you have explictly specified the column
  3088.                     // and the col is listed here..
  3089.                     // not sure if 1:1 table could cause probs here..
  3090.                     
  3091.                     if ($joinCol !== false) {
  3092.                         $this->raiseError( 
  3093.                             "joinAdd: You cannot target a join column in the " .
  3094.                             "'link from' table ({$obj->__table}). " . 
  3095.                             "Either remove the fourth argument to joinAdd() ".
  3096.                             "({$joinCol}), or alter your links.ini file.",
  3097.                             DB_DATAOBJECT_ERROR_NODATA);
  3098.                         return false;
  3099.                     }
  3100.                 
  3101.                     $ofield = $k;
  3102.                     $tfield = $ar[1];
  3103.                     break;
  3104.                 }
  3105.             }
  3106.         }
  3107.  
  3108.         /* otherwise see if there are any links from this table to the obj. */
  3109.         //print_r($this->links());
  3110.         if (($ofield === false) && ($links = $this->links())) {
  3111.             foreach ($links as $k => $v) {
  3112.                 /* link contains {this column} = {linked table}:{linked column} */
  3113.                 $ar = explode(':', $v);
  3114.                 if ($ar[0] == $obj->__table) {
  3115.                     if ($joinCol !== false) {
  3116.                         if ($k == $joinCol) {
  3117.                             $tfield = $k;
  3118.                             $ofield = $ar[1];
  3119.                             break;
  3120.                         } else {
  3121.                             continue;
  3122.                         }
  3123.                     } else {
  3124.                         $tfield = $k;
  3125.                         $ofield = $ar[1];
  3126.                         break;
  3127.                     }
  3128.                 }
  3129.             }
  3130.         }
  3131.         // finally if these two table have column names that match do a join by default on them
  3132.  
  3133.         if (($ofield === false) && $joinCol) {
  3134.             $ofield = $joinCol;
  3135.             $tfield = $joinCol;
  3136.  
  3137.         }
  3138.         /* did I find a conneciton between them? */
  3139.  
  3140.         if ($ofield === false) {
  3141.             $this->raiseError(
  3142.                 "joinAdd: {$obj->__table} has no link with {$this->__table}",
  3143.                 DB_DATAOBJECT_ERROR_NODATA);
  3144.             return false;
  3145.         }
  3146.         $joinType = strtoupper($joinType);
  3147.         
  3148.         // we default to joining as the same name (this is remvoed later..)
  3149.         
  3150.         if ($joinAs === false) {
  3151.             $joinAs = $obj->__table;
  3152.         }
  3153.         
  3154.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  3155.         
  3156.         // not sure  how portable adding database prefixes is..
  3157.         $objTable = $quoteIdentifiers ? 
  3158.                 $DB->quoteIdentifier($obj->__table) : 
  3159.                  $obj->__table ;
  3160.                 
  3161.         $dbPrefix  = '';
  3162.         if (strlen($obj->_database) && in_array($DB->dsn['phptype'],array('mysql','mysqli'))) {
  3163.             $dbPrefix = ($quoteIdentifiers
  3164.                          ? $DB->quoteIdentifier($obj->_database)
  3165.                          : $obj->_database) . '.';    
  3166.         }
  3167.         
  3168.         // if they are the same, then dont add a prefix...                
  3169.         if ($obj->_database == $this->_database) {
  3170.            $dbPrefix = '';
  3171.         }
  3172.         // as far as we know only mysql supports database prefixes..
  3173.         // prefixing the database name is now the default behaviour,
  3174.         // as it enables joining mutiple columns from multiple databases...
  3175.          
  3176.             // prefix database (quoted if neccessary..)
  3177.         $objTable = $dbPrefix . $objTable;
  3178.        
  3179.          
  3180.         
  3181.         
  3182.         
  3183.         // nested (join of joined objects..)
  3184.         $appendJoin = '';
  3185.         if ($obj->_join) {
  3186.             // postgres allows nested queries, with ()'s
  3187.             // not sure what the results are with other databases..
  3188.             // may be unpredictable..
  3189.             if (in_array($DB->dsn["phptype"],array('pgsql'))) {
  3190.                 $objTable = "($objTable {$obj->_join})";
  3191.             } else {
  3192.                 $appendJoin = $obj->_join;
  3193.             }
  3194.         }
  3195.         
  3196.         
  3197.         $table = $this->__table;
  3198.         
  3199.         if ($quoteIdentifiers) {
  3200.             $joinAs   = $DB->quoteIdentifier($joinAs);
  3201.             $table    = $DB->quoteIdentifier($table);     
  3202.             $ofield   = $DB->quoteIdentifier($ofield);    
  3203.             $tfield   = $DB->quoteIdentifier($tfield);    
  3204.         }
  3205.         // add database prefix if they are different databases
  3206.        
  3207.         
  3208.         $fullJoinAs = '';
  3209.         $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs;
  3210.         if ($addJoinAs) {
  3211.             // join table a AS b - is only supported by a few databases and is probably not needed
  3212.             // , however since it makes the whole Statement alot clearer we are leaving it in
  3213.             // for those databases.
  3214.             $fullJoinAs = in_array($DB->dsn["phptype"],array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" :  $joinAs;
  3215.         } else {
  3216.             // if 
  3217.             $joinAs = $dbPrefix . $joinAs;
  3218.         }
  3219.         
  3220.         
  3221.         switch ($joinType) {
  3222.             case 'INNER':
  3223.             case 'LEFT': 
  3224.             case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
  3225.                 
  3226.                 // Feature Request #4266 - Allow joins with multiple keys
  3227.                 $this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
  3228.                 if (is_array($ofield)) {
  3229.                     $key_count = count($ofield);
  3230.                     for($i = 0; $i < $key_count; $i++) {
  3231.                         if ($i == 0) {
  3232.                             $this->_join .= " ON {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} {$appendJoin} ";
  3233.                         }
  3234.                         else {
  3235.                             $this->_join .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} {$appendJoin} ";
  3236.                         }
  3237.                      }
  3238.                 } else {
  3239.                     $this->_join .= " ON {$joinAs}.{$ofield}={$table}.{$tfield} {$appendJoin} ";
  3240.                 }
  3241.  
  3242.                 break;
  3243.                 
  3244.             case '': // this is just a standard multitable select..
  3245.                 $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
  3246.                 $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
  3247.         }
  3248.          
  3249.         // if obj only a dataobject - eg. no extended class has been defined..
  3250.         // it obvioulsy cant work out what child elements might exist...
  3251.         // untill we get on the fly querying of tables..
  3252.         if ( strtolower(get_class($obj)) == 'db_dataobject') {
  3253.             return true;
  3254.         }
  3255.          
  3256.         /* now add where conditions for anything that is set in the object */
  3257.     
  3258.     
  3259.     
  3260.         $items = $obj->table();
  3261.         // will return an array if no items..
  3262.         
  3263.         // only fail if we where expecting it to work (eg. not joined on a array)
  3264.         
  3265.         
  3266.         
  3267.         if (!$items) {
  3268.             $this->raiseError(
  3269.                 "joinAdd: No table definition for {$obj->__table}", 
  3270.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  3271.             return false;
  3272.         }
  3273.  
  3274.         foreach($items as $k => $v) {
  3275.             if (!isset($obj->$k)) {
  3276.                 continue;
  3277.             }
  3278.             
  3279.             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  3280.             
  3281.             
  3282.             if ($v & DB_DATAOBJECT_STR) {
  3283.                 $this->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
  3284.                         ($v & DB_DATAOBJECT_BOOL) ? 
  3285.                             // this is thanks to the braindead idea of postgres to 
  3286.                             // use t/f for boolean.
  3287.                             (($obj->$k === 'f') ? 0 : (int)(bool) $obj->$k) :  
  3288.                             $obj->$k
  3289.                     )));
  3290.                 continue;
  3291.             }
  3292.             if (is_numeric($obj->$k)) {
  3293.                 $this->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
  3294.                 continue;
  3295.             }
  3296.                         
  3297.             if (is_a($obj->$k,'DB_DataObject_Cast')) {
  3298.                 $value = $obj->$k->toString($v,$DB);
  3299.                 if (PEAR::isError($value)) {
  3300.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  3301.                     return false;
  3302.                 }
  3303.                 if (strtolower($value) === 'null') {
  3304.                     $this->whereAdd("{$joinAs}.{$kSql} IS NULL");
  3305.                     continue;
  3306.                 } else {
  3307.                     $this->whereAdd("{$joinAs}.{$kSql} = $value");
  3308.                     continue;
  3309.                 }
  3310.             }
  3311.             
  3312.             
  3313.             /* this is probably an error condition! */
  3314.             $this->whereAdd("{$joinAs}.{$kSql} = 0");
  3315.         }
  3316.         if (!isset($this->_query)) {
  3317.             $this->raiseError(
  3318.                 "joinAdd can not be run from a object that has had a query run on it,
  3319.                 clone the object or create a new one and use setFrom()", 
  3320.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  3321.             return false;
  3322.         }
  3323.         // and finally merge the whereAdd from the child..
  3324.         if (!$obj->_query['condition']) {
  3325.             return true;
  3326.         }
  3327.         $cond = preg_replace('/^\sWHERE/i','',$obj->_query['condition']);
  3328.         
  3329.         $this->whereAdd("($cond)");
  3330.         return true;
  3331.  
  3332.     }
  3333.  
  3334.     /**
  3335.      * Copies items that are in the table definitions from an
  3336.      * array or object into the current object
  3337.      * will not override key values.
  3338.      *
  3339.      *
  3340.      * @param    array | object  $from
  3341.      * @param    string  $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
  3342.      * @param    boolean  $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
  3343.      * @access   public
  3344.      * @return   true on success or array of key=>setValue error message
  3345.      */
  3346.     function setFrom($from, $format = '%s', $skipEmpty=false)
  3347.     {
  3348.         global $_DB_DATAOBJECT;
  3349.         $keys  = $this->keys();
  3350.         $items = $this->table();
  3351.         if (!$items) {
  3352.             $this->raiseError(
  3353.                 "setFrom:Could not find table definition for {$this->__table}", 
  3354.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  3355.             return;
  3356.         }
  3357.         $overload_return = array();
  3358.         foreach (array_keys($items) as $k) {
  3359.             if (in_array($k,$keys)) {
  3360.                 continue; // dont overwrite keys
  3361.             }
  3362.             if (!$k) {
  3363.                 continue; // ignore empty keys!!! what
  3364.             }
  3365.             if (is_object($from) && isset($from->{sprintf($format,$k)})) {
  3366.                 $kk = (strtolower($k) == 'from') ? '_from' : $k;
  3367.                 if (method_exists($this,'set'.$kk)) {
  3368.                     $ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});
  3369.                     if (is_string($ret)) {
  3370.                         $overload_return[$k] = $ret;
  3371.                     }
  3372.                     continue;
  3373.                 }
  3374.                 $this->$k = $from->{sprintf($format,$k)};
  3375.                 continue;
  3376.             }
  3377.             
  3378.             if (is_object($from)) {
  3379.                 continue;
  3380.             }
  3381.             
  3382.             if (empty($from[$k]) && $skipEmpty) {
  3383.                 continue;
  3384.             }
  3385.             
  3386.             if (!isset($from[sprintf($format,$k)])) {
  3387.                 continue;
  3388.             }
  3389.            
  3390.             $kk = (strtolower($k) == 'from') ? '_from' : $k;
  3391.             if (method_exists($this,'set'. $kk)) {
  3392.                 $ret =  $this->{'set'.$kk}($from[sprintf($format,$k)]);
  3393.                 if (is_string($ret)) {
  3394.                     $overload_return[$k] = $ret;
  3395.                 }
  3396.                 continue;
  3397.             }
  3398.             if (is_object($from[sprintf($format,$k)])) {
  3399.                 continue;
  3400.             }
  3401.             if (is_array($from[sprintf($format,$k)])) {
  3402.                 continue;
  3403.             }
  3404.             $ret = $this->fromValue($k,$from[sprintf($format,$k)]);
  3405.             if ($ret !== true)  {
  3406.                 $overload_return[$k] = 'Not A Valid Value';
  3407.             }
  3408.             //$this->$k = $from[sprintf($format,$k)];
  3409.         }
  3410.         if ($overload_return) {
  3411.             return $overload_return;
  3412.         }
  3413.         return true;
  3414.     }
  3415.  
  3416.     /**
  3417.      * Returns an associative array from the current data
  3418.      * (kind of oblivates the idea behind DataObjects, but
  3419.      * is usefull if you use it with things like QuickForms.
  3420.      *
  3421.      * you can use the format to return things like user[key]
  3422.      * by sending it $object->toArray('user[%s]')
  3423.      *
  3424.      * will also return links converted to arrays.
  3425.      *
  3426.      * @param   string  sprintf format for array
  3427.      * @param   bool    empty only return elemnts that have a value set.
  3428.      *
  3429.      * @access   public
  3430.      * @return   array of key => value for row
  3431.      */
  3432.  
  3433.     function toArray($format = '%s', $hideEmpty = false) 
  3434.     {
  3435.         global $_DB_DATAOBJECT;
  3436.         $ret = array();
  3437.         $rf = ($this->_resultFields !== false) ? $this->_resultFields : 
  3438.                 (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ? $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
  3439.         $ar = ($rf !== false) ?
  3440.             array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
  3441.             $this->table();
  3442.  
  3443.         foreach($ar as $k=>$v) {
  3444.              
  3445.             if (!isset($this->$k)) {
  3446.                 if (!$hideEmpty) {
  3447.                     $ret[sprintf($format,$k)] = '';
  3448.                 }
  3449.                 continue;
  3450.             }
  3451.             // call the overloaded getXXXX() method. - except getLink and getLinks
  3452.             if (method_exists($this,'get'.$k) && !in_array(strtolower($k),array('links','link'))) {
  3453.                 $ret[sprintf($format,$k)] = $this->{'get'.$k}();
  3454.                 continue;
  3455.             }
  3456.             // should this call toValue() ???
  3457.             $ret[sprintf($format,$k)] = $this->$k;
  3458.         }
  3459.         if (!$this->_link_loaded) {
  3460.             return $ret;
  3461.         }
  3462.         foreach($this->_link_loaded as $k) {
  3463.             $ret[sprintf($format,$k)] = $this->$k->toArray();
  3464.         
  3465.         }
  3466.         
  3467.         return $ret;
  3468.     }
  3469.  
  3470.     /**
  3471.      * validate the values of the object (usually prior to inserting/updating..)
  3472.      *
  3473.      * Note: This was always intended as a simple validation routine.
  3474.      * It lacks understanding of field length, whether you are inserting or updating (and hence null key values)
  3475.      *
  3476.      * This should be moved to another class: DB_DataObject_Validate 
  3477.      *      FEEL FREE TO SEND ME YOUR VERSION FOR CONSIDERATION!!!
  3478.      *
  3479.      * Usage:
  3480.      * if (is_array($ret = $obj->validate())) { ... there are problems with the data ... }
  3481.      *
  3482.      * Logic:
  3483.      *   - defaults to only testing strings/numbers if numbers or strings are the correct type and null values are correct
  3484.      *   - validate Column methods : "validate{ROWNAME}()"  are called if they are defined.
  3485.      *            These methods should return 
  3486.      *                  true = everything ok
  3487.      *                  false|object = something is wrong!
  3488.      * 
  3489.      *   - This method loads and uses the PEAR Validate Class.
  3490.      *
  3491.      *
  3492.      * @access  public
  3493.      * @return  array of validation results (where key=>value, value=false|object if it failed) or true (if they all succeeded)
  3494.      */
  3495.     function validate()
  3496.     {
  3497.         require_once 'Validate.php';
  3498.         $table = $this->table();
  3499.         $ret   = array();
  3500.         $seq   = $this->sequenceKey();
  3501.         
  3502.         foreach($table as $key => $val) {
  3503.             
  3504.             
  3505.             // call user defined validation always...
  3506.             $method = "Validate" . ucfirst($key);
  3507.             if (method_exists($this, $method)) {
  3508.                 $ret[$key] = $this->$method();
  3509.                 continue;
  3510.             }
  3511.             
  3512.             // if not null - and it's not set.......
  3513.             
  3514.             if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
  3515.                 // dont check empty sequence key values..
  3516.                 if (($key == $seq[0]) && ($seq[1] == true)) {
  3517.                     continue;
  3518.                 }
  3519.                 $ret[$key] = false;
  3520.                 continue;
  3521.             }
  3522.             
  3523.             
  3524.             if (is_string($this->$key) && (strtolower($this->$key) == 'null')) {
  3525.                 if ($val & DB_DATAOBJECT_NOTNULL) {
  3526.                     $this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4);
  3527.                     $ret[$key] = false;
  3528.                     continue;
  3529.                 }
  3530.                 continue;
  3531.             }
  3532.  
  3533.             // ignore things that are not set. ?
  3534.            
  3535.             if (!isset($this->$key)) {
  3536.                 continue;
  3537.             }
  3538.             
  3539.             // if the string is empty.. assume it is ok..
  3540.             if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
  3541.                 continue;
  3542.             }
  3543.             
  3544.             // dont try and validate cast objects - assume they are problably ok..
  3545.             if (is_object($this->$key) && is_a($this->$key,'DB_DataObject_Cast')) {
  3546.                 continue;
  3547.             }
  3548.             // at this point if you have set something to an object, and it's not expected
  3549.             // the Validate will probably break!!... - rightly so! (your design is broken, 
  3550.             // so issuing a runtime error like PEAR_Error is probably not appropriate..
  3551.             
  3552.             switch (true) {
  3553.                 // todo: date time.....
  3554.                 case  ($val & DB_DATAOBJECT_STR):
  3555.                     $ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
  3556.                     continue;
  3557.                 case  ($val & DB_DATAOBJECT_INT):
  3558.                     $ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
  3559.                     continue;
  3560.             }
  3561.         }
  3562.         // if any of the results are false or an object (eg. PEAR_Error).. then return the array..
  3563.         foreach ($ret as $key => $val) {
  3564.             if ($val !== true) {
  3565.                 return $ret;
  3566.             }
  3567.         }
  3568.         return true; // everything is OK.
  3569.     }
  3570.  
  3571.     /**
  3572.      * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
  3573.      *
  3574.      * @access public
  3575.      * @return object The DB connection
  3576.      */
  3577.     function &getDatabaseConnection()
  3578.     {
  3579.         global $_DB_DATAOBJECT;
  3580.  
  3581.         if (($e = $this->_connect()) !== true) {
  3582.             return $e;
  3583.         }
  3584.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  3585.             $r = false;
  3586.             return $r;
  3587.         }
  3588.         return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  3589.     }
  3590.  
  3591.  
  3592.     /**
  3593.      * Gets the DB result object related to the objects active query
  3594.      *  - so you can use funky pear stuff with it - like pager for example.. :)
  3595.      *
  3596.      * @access public
  3597.      * @return object The DB result object
  3598.      */
  3599.      
  3600.     function &getDatabaseResult()
  3601.     {
  3602.         global $_DB_DATAOBJECT;
  3603.         $this->_connect();
  3604.         if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
  3605.             $r = false;
  3606.             return $r;
  3607.         }
  3608.         return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  3609.     }
  3610.  
  3611.     /**
  3612.      * Overload Extension support
  3613.      *  - enables setCOLNAME/getCOLNAME
  3614.      *  if you define a set/get method for the item it will be called.
  3615.      * otherwise it will just return/set the value.
  3616.      * NOTE this currently means that a few Names are NO-NO's 
  3617.      * eg. links,link,linksarray, from, Databaseconnection,databaseresult
  3618.      *
  3619.      * note 
  3620.      *  - set is automatically called by setFrom.
  3621.      *   - get is automatically called by toArray()
  3622.      *  
  3623.      * setters return true on success. = strings on failure
  3624.      * getters return the value!
  3625.      *
  3626.      * this fires off trigger_error - if any problems.. pear_error, 
  3627.      * has problems with 4.3.2RC2 here
  3628.      *
  3629.      * @access public
  3630.      * @return true?
  3631.      * @see overload
  3632.      */
  3633.  
  3634.     
  3635.     function _call($method,$params,&$return) {
  3636.         
  3637.         //$this->debug("ATTEMPTING OVERLOAD? $method");
  3638.         // ignore constructors : - mm
  3639.         if (strtolower($method) == strtolower(get_class($this))) {
  3640.             return true;
  3641.         }
  3642.         $type = strtolower(substr($method,0,3));
  3643.         $class = get_class($this);
  3644.         if (($type != 'set') && ($type != 'get')) {
  3645.             return false;
  3646.         }
  3647.          
  3648.         
  3649.         
  3650.         // deal with naming conflick of setFrom = this is messy ATM!
  3651.         
  3652.         if (strtolower($method) == 'set_from') {
  3653.             $return = $this->toValue('from',isset($params[0]) ? $params[0] : null);
  3654.             return  true;
  3655.         }
  3656.         
  3657.         $element = substr($method,3);
  3658.         
  3659.         // dont you just love php's case insensitivity!!!!
  3660.         
  3661.         $array =  array_keys(get_class_vars($class));
  3662.         /* php5 version which segfaults on 5.0.3 */
  3663.         if (class_exists('ReflectionClass')) {
  3664.             $reflection = new ReflectionClass($class);
  3665.             $array = array_keys($reflection->getdefaultProperties());
  3666.         }
  3667.         
  3668.         if (!in_array($element,$array)) {
  3669.             // munge case
  3670.             foreach($array as $k) {
  3671.                 $case[strtolower($k)] = $k;
  3672.             }
  3673.             if ((substr(phpversion(),0,1) == 5) && isset($case[strtolower($element)])) {
  3674.                 trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
  3675.                 $element = strtolower($element);
  3676.             }
  3677.             
  3678.             // does it really exist?
  3679.             if (!isset($case[$element])) {
  3680.                 return false;            
  3681.             }
  3682.             // use the mundged case
  3683.             $element = $case[$element]; // real case !
  3684.         }
  3685.         
  3686.         
  3687.         if ($type == 'get') {
  3688.             $return = $this->toValue($element,isset($params[0]) ? $params[0] : null);
  3689.             return true;
  3690.         }
  3691.         
  3692.         
  3693.         $return = $this->fromValue($element, $params[0]);
  3694.          
  3695.         return true;
  3696.             
  3697.           
  3698.     }
  3699.         
  3700.     
  3701.     /**
  3702.     * standard set* implementation.
  3703.     *
  3704.     * takes data and uses it to set dates/strings etc.
  3705.     * normally called from __call..  
  3706.     *
  3707.     * Current supports
  3708.     *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
  3709.     *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
  3710.     *
  3711.     *   time      = using strtotime 
  3712.     *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
  3713.     *   string    = typecast only..
  3714.     * 
  3715.     * TODO: add formater:: eg. d/m/Y for date! ???
  3716.     *
  3717.     * @param   string       column of database
  3718.     * @param   mixed        value to assign
  3719.     *
  3720.     * @return   true| false     (False on error)
  3721.     * @access   public 
  3722.     * @see      DB_DataObject::_call
  3723.     */
  3724.   
  3725.     
  3726.     function fromValue($col,$value) 
  3727.     {
  3728.         $cols = $this->table();
  3729.         // dont know anything about this col..
  3730.         if (!isset($cols[$col])) {
  3731.             $this->$col = $value;
  3732.             return true;
  3733.         }
  3734.         //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
  3735.         switch (true) {
  3736.             // set to null and column is can be null...
  3737.             case ((strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
  3738.             case (is_object($value) && is_a($value,'DB_DataObject_Cast')): 
  3739.                 $this->$col = $value;
  3740.                 return true;
  3741.                 
  3742.             // fail on setting null on a not null field..
  3743.             case ((strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
  3744.                 return false;
  3745.         
  3746.             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
  3747.                 // empty values get set to '' (which is inserted/updated as NULl
  3748.                 if (!$value) {
  3749.                     $this->$col = '';
  3750.                 }
  3751.             
  3752.                 if (is_numeric($value)) {
  3753.                     $this->$col = date('Y-m-d H:i:s', $value);
  3754.                     return true;
  3755.                 }
  3756.               
  3757.                 // eak... - no way to validate date time otherwise...
  3758.                 $this->$col = (string) $value;
  3759.                 return true;
  3760.             
  3761.             case ($cols[$col] & DB_DATAOBJECT_DATE):
  3762.                 // empty values get set to '' (which is inserted/updated as NULl
  3763.                  
  3764.                 if (!$value) {
  3765.                     $this->$col = '';
  3766.                     return true; 
  3767.                 }
  3768.             
  3769.                 if (is_numeric($value)) {
  3770.                     $this->$col = date('Y-m-d',$value);
  3771.                     return true;
  3772.                 }
  3773.                  
  3774.                 // try date!!!!
  3775.                 require_once 'Date.php';
  3776.                 $x = new Date($value);
  3777.                 $this->$col = $x->format("%Y-%m-%d");
  3778.                 return true;
  3779.             
  3780.             case ($cols[$col] & DB_DATAOBJECT_TIME):
  3781.                 // empty values get set to '' (which is inserted/updated as NULl
  3782.                 if (!$value) {
  3783.                     $this->$col = '';
  3784.                 }
  3785.             
  3786.                 $guess = strtotime($value);
  3787.                 if ($guess != -1) {
  3788.                      $this->$col = date('H:i:s', $guess);
  3789.                     return $return = true;
  3790.                 }
  3791.                 // otherwise an error in type...
  3792.                 return false;
  3793.             
  3794.             case ($cols[$col] & DB_DATAOBJECT_STR):
  3795.                 
  3796.                 $this->$col = (string) $value;
  3797.                 return true;
  3798.                 
  3799.             // todo : floats numerics and ints...
  3800.             default:
  3801.                 $this->$col = $value;
  3802.                 return true;
  3803.         }
  3804.     
  3805.     
  3806.     
  3807.     }
  3808.      /**
  3809.     * standard get* implementation.
  3810.     *
  3811.     *  with formaters..
  3812.     * supported formaters:  
  3813.     *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date 
  3814.     *   numbers   : %02d (eg. sprintf)
  3815.     *  NOTE you will get unexpected results with times like 0000-00-00 !!!
  3816.     *
  3817.     *
  3818.     * 
  3819.     * @param   string       column of database
  3820.     * @param   format       foramt
  3821.     *
  3822.     * @return   true     Description
  3823.     * @access   public 
  3824.     * @see      DB_DataObject::_call(),strftime(),Date::format()
  3825.     */
  3826.     function toValue($col,$format = null) 
  3827.     {
  3828.         if (is_null($format)) {
  3829.             return $this->$col;
  3830.         }
  3831.         $cols = $this->table();
  3832.         switch (true) {
  3833.             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
  3834.                 if (!$this->$col) {
  3835.                     return '';
  3836.                 }
  3837.                 $guess = strtotime($this->$col);
  3838.                 if ($guess != -1) {
  3839.                     return strftime($format, $guess);
  3840.                 }
  3841.                 // eak... - no way to validate date time otherwise...
  3842.                 return $this->$col;
  3843.             case ($cols[$col] & DB_DATAOBJECT_DATE):
  3844.                 if (!$this->$col) {
  3845.                     return '';
  3846.                 } 
  3847.                 $guess = strtotime($this->$col);
  3848.                 if ($guess != -1) {
  3849.                     return strftime($format,$guess);
  3850.                 }
  3851.                 // try date!!!!
  3852.                 require_once 'Date.php';
  3853.                 $x = new Date($this->$col);
  3854.                 return $x->format($format);
  3855.                 
  3856.             case ($cols[$col] & DB_DATAOBJECT_TIME):
  3857.                 if (!$this->$col) {
  3858.                     return '';
  3859.                 }
  3860.                 $guess = strtotime($this->$col);
  3861.                 if ($guess > -1) {
  3862.                     return strftime($format, $guess);
  3863.                 }
  3864.                 // otherwise an error in type...
  3865.                 return $this->$col;
  3866.                 
  3867.             case ($cols[$col] &  DB_DATAOBJECT_MYSQLTIMESTAMP):
  3868.                 if (!$this->$col) {
  3869.                     return '';
  3870.                 }
  3871.                 require_once 'Date.php';
  3872.                 
  3873.                 $x = new Date($this->$col);
  3874.                 
  3875.                 return $x->format($format);
  3876.             
  3877.              
  3878.             case ($cols[$col] &  DB_DATAOBJECT_BOOL):
  3879.                 
  3880.                 if ($cols[$col] &  DB_DATAOBJECT_STR) {
  3881.                     // it's a 't'/'f' !
  3882.                     return ($this->$col === 't');
  3883.                 }
  3884.                 return (bool) $this->$col;
  3885.             
  3886.                
  3887.             default:
  3888.                 return sprintf($format,$this->col);
  3889.         }
  3890.             
  3891.  
  3892.     }
  3893.     
  3894.     
  3895.     /* ----------------------- Debugger ------------------ */
  3896.  
  3897.     /**
  3898.      * Debugger. - use this in your extended classes to output debugging information.
  3899.      *
  3900.      * Uses DB_DataObject::DebugLevel(x) to turn it on
  3901.      *
  3902.      * @param    string $message - message to output
  3903.      * @param    string $logtype - bold at start
  3904.      * @param    string $level   - output level
  3905.      * @access   public
  3906.      * @return   none
  3907.      */
  3908.     function debug($message, $logtype = 0, $level = 1)
  3909.     {
  3910.         global $_DB_DATAOBJECT;
  3911.  
  3912.         if (empty($_DB_DATAOBJECT['CONFIG']['debug'])  || 
  3913.             (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) &&  $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
  3914.             return;
  3915.         }
  3916.         // this is a bit flaky due to php's wonderfull class passing around crap..
  3917.         // but it's about as good as it gets..
  3918.         $class = (isset($this) && is_a($this,'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
  3919.         
  3920.         if (!is_string($message)) {
  3921.             $message = print_r($message,true);
  3922.         }
  3923.         if (!is_numeric( $_DB_DATAOBJECT['CONFIG']['debug']) && is_callable( $_DB_DATAOBJECT['CONFIG']['debug'])) {
  3924.             return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
  3925.         }
  3926.         
  3927.         if (!ini_get('html_errors')) {
  3928.             echo "$class   : $logtype       : $message\n";
  3929.             flush();
  3930.             return;
  3931.         }
  3932.         if (!is_string($message)) {
  3933.             $message = print_r($message,true);
  3934.         }
  3935.         $colorize = ($logtype == 'ERROR') ? '<font color="red">' : '<font>';
  3936.         echo "<code>{$colorize}<B>$class: $logtype:</B> ". nl2br(htmlspecialchars($message)) . "</font></code><BR>\n";
  3937.     }
  3938.  
  3939.     /**
  3940.      * sets and returns debug level
  3941.      * eg. DB_DataObject::debugLevel(4);
  3942.      *
  3943.      * @param   int     $v  level
  3944.      * @access  public
  3945.      * @return  none
  3946.      */
  3947.     function debugLevel($v = null)
  3948.     {
  3949.         global $_DB_DATAOBJECT;
  3950.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  3951.             DB_DataObject::_loadConfig();
  3952.         }
  3953.         if ($v !== null) {
  3954.             $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
  3955.             $_DB_DATAOBJECT['CONFIG']['debug']  = $v;
  3956.             return $r;
  3957.         }
  3958.         return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
  3959.     }
  3960.  
  3961.     /**
  3962.      * Last Error that has occured
  3963.      * - use $this->_lastError or
  3964.      * $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
  3965.      *
  3966.      * @access  public
  3967.      * @var     object PEAR_Error (or false)
  3968.      */
  3969.     var $_lastError = false;
  3970.  
  3971.     /**
  3972.      * Default error handling is to create a pear error, but never return it.
  3973.      * if you need to handle errors you should look at setting the PEAR_Error callback
  3974.      * this is due to the fact it would wreck havoc on the internal methods!
  3975.      *
  3976.      * @param  int $message    message
  3977.      * @param  int $type       type
  3978.      * @param  int $behaviour  behaviour (die or continue!);
  3979.      * @access public
  3980.      * @return error object
  3981.      */
  3982.     function raiseError($message, $type = null, $behaviour = null)
  3983.     {
  3984.         global $_DB_DATAOBJECT;
  3985.         
  3986.         if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
  3987.             $behaviour = null;
  3988.         }
  3989.         $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
  3990.         
  3991.         // this will never work totally with PHP's object model.
  3992.         // as this is passed on static calls (like staticGet in our case)
  3993.  
  3994.         if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
  3995.             $this->_lastError = $error;
  3996.         }
  3997.  
  3998.         $_DB_DATAOBJECT['LASTERROR'] = $error;
  3999.  
  4000.         // no checks for production here?....... - we log  errors before we throw them.
  4001.         DB_DataObject::debug($message,'ERROR',1);
  4002.         
  4003.         
  4004.         if (PEAR::isError($message)) {
  4005.             $error = $message;
  4006.         } else {
  4007.             require_once 'DB/DataObject/Error.php';
  4008.             $error = PEAR::raiseError($message, $type, $behaviour,
  4009.                             $opts=null, $userinfo=null, 'DB_DataObject_Error'
  4010.                         );
  4011.         }
  4012.    
  4013.         return $error;
  4014.     }
  4015.  
  4016.     /**
  4017.      * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to  PEAR::getStaticProperty('DB_DataObject','options');
  4018.      *
  4019.      * After Profiling DB_DataObject, I discoved that the debug calls where taking
  4020.      * considerable time (well 0.1 ms), so this should stop those calls happening. as
  4021.      * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton
  4022.      * THIS STILL NEEDS FURTHER INVESTIGATION
  4023.      *
  4024.      * @access   public
  4025.      * @return   object an error object
  4026.      */
  4027.     function _loadConfig()
  4028.     {
  4029.         global $_DB_DATAOBJECT;
  4030.  
  4031.         $_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject','options');
  4032.  
  4033.  
  4034.     }
  4035.      /**
  4036.      * Free global arrays associated with this object.
  4037.      *
  4038.      *
  4039.      * @access   public
  4040.      * @return   none
  4041.      */
  4042.     function free() 
  4043.     {
  4044.         global $_DB_DATAOBJECT;
  4045.           
  4046.         if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  4047.             unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  4048.         }
  4049.         if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {     
  4050.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  4051.         }
  4052.         // clear the staticGet cache as well.
  4053.         $this->_clear_cache();
  4054.         // this is a huge bug in DB!
  4055.         if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  4056.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
  4057.         }
  4058.         
  4059.     }
  4060.     
  4061.     
  4062.     /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
  4063.     
  4064.     function _get_table() { return $this->table(); }
  4065.     function _get_keys()  { return $this->keys();  }
  4066.     
  4067.     
  4068.     
  4069.     
  4070. }
  4071. // technially 4.3.2RC1 was broken!!
  4072. // looks like 4.3.3 may have problems too....
  4073. if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
  4074.  
  4075.     if ((phpversion() != '4.3.2-RC1') && (version_compare( phpversion(), "4.3.1") > 0)) {
  4076.         if (version_compare( phpversion(), "5") < 0) {
  4077.            overload('DB_DataObject');
  4078.         } 
  4079.         $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = true;
  4080.     }
  4081. }
  4082.  
  4083.